diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Article.swift b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Article.swift
new file mode 100644
index 00000000..4ecf602f
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Article.swift
@@ -0,0 +1,43 @@
+//
+// Article.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/15/21.
+//
+
+import Foundation
+import Segment
+
+struct Article {
+ let title: String
+ let source: String
+ let imageName: String
+}
+
+extension Article {
+
+ private static let availableLibDocs = [
+ Article(title:"Analytics for iOS ", source: "https://segment.com/docs/connections/sources/catalog/libraries/mobile/ios/#analytics-for-ios", imageName: "Segment_logo"),
+ Article(title:"Analytics for Android", source: "https://segment.com/docs/connections/sources/catalog/libraries/mobile/android/", imageName: "Segment_logo"),
+ Article(title:"Analytics for React Native", source: "https://segment.com/docs/connections/sources/catalog/libraries/mobile/react-native/", imageName: "Segment_logo"),
+ Article(title:"Analytics.js", source: "https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/", imageName: "Segment_logo"),
+ Article(title:"Analytics for Node ", source: "https://segment.com/docs/connections/sources/catalog/libraries/server/node/", imageName: "Segment_logo"),
+ ]
+
+ private static let availableDestDocs = [
+ Article(title:"Appsflyer Destination", source: "https://segment.com/docs/connections/destinations/catalog/appsflyer/", imageName: "Segment_logo"),
+ Article(title:"Firebase Destination", source: "https://segment.com/docs/connections/destinations/catalog/firebase/", imageName: "Segment_logo"),
+ Article(title:"Facebook App Events Destination", source: "https://segment.com/docs/connections/destinations/catalog/facebook-app-events/", imageName: "Segment_logo"),
+ Article(title:"Mixpanel Destination", source: "https://segment.com/docs/connections/destinations/catalog/mixpanel/", imageName: "Segment_logo"),
+ Article(title:"Amplitude Destination", source: "https://segment.com/docs/connections/destinations/catalog/Amplitude/", imageName: "Segment_logo"),
+
+ ]
+
+ static var libDocs: [Article] {
+ return Array(availableLibDocs.shuffled().prefix(2))
+ }
+
+ static var destDocs: [Article] {
+ return Array(availableDestDocs.shuffled().prefix(2))
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleItemView.swift b/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleItemView.swift
new file mode 100644
index 00000000..e43ab753
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleItemView.swift
@@ -0,0 +1,32 @@
+//
+// ArticleItemView.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/15/21.
+//
+
+import SwiftUI
+import Segment
+
+struct ArticleItemView: View {
+ var article : Article
+
+ var body: some View {
+ HStack {
+ Image(article.imageName)
+ .resizable()
+ .frame(width: 50, height: 50, alignment: .center)
+ .cornerRadius(8)
+ VStack(alignment: .leading) {
+ Text(article.title)
+ .lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/)
+ .font(.system(size: 14, weight: .semibold, design: .default))
+ Text(article.source)
+ .lineLimit(1)
+ .font(.caption2)
+ .foregroundColor(.gray)
+ }
+ }
+ }
+}
+
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleSectionView.swift b/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleSectionView.swift
new file mode 100644
index 00000000..2bcd80dd
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleSectionView.swift
@@ -0,0 +1,27 @@
+//
+// ArticleSectionView.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/15/21.
+//
+
+import SwiftUI
+import Segment
+
+struct ArticleSectionView: View {
+
+ var sectionTitle: String
+ var articles: [Article]
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ Text(sectionTitle)
+ .font(.subheadline)
+ .fontWeight(.heavy)
+ .foregroundColor(Color.blue)
+ ArticleItemView(article: articles[0])
+ ArticleItemView(article: articles[1])
+ }
+ }
+}
+
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleWidget.swift b/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleWidget.swift
new file mode 100644
index 00000000..6bbba8ba
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/ArticleWidget.swift
@@ -0,0 +1,105 @@
+//
+// ArticleWidget.swift
+// ArticleWidget
+//
+// Created by Alan Charles on 8/15/21.
+//
+
+import Segment
+import WidgetKit
+import SwiftUI
+
+struct Provider: TimelineProvider {
+
+ func placeholder(in context: TimelineProvider.Context) -> ArticleEntry {
+ ArticleEntry(date: Date(),
+ libArticles: Article.libDocs,
+ destArticles: Article.destDocs)
+ }
+
+ func getSnapshot(in context: TimelineProvider.Context, completion: @escaping (ArticleEntry) -> ()) {
+ let entry = ArticleEntry(date: Date(),
+ libArticles: Article.libDocs,
+ destArticles: Article.destDocs)
+ completion(entry)
+
+ Analytics.main.track(name:"Widget Snapshot")
+ }
+
+ func getTimeline(in context: TimelineProvider.Context, completion: @escaping (WidgetKit.Timeline) -> ()) {
+ var entries: [ArticleEntry] = []
+
+ // Generate a timeline consisting of five entries an hour apart, starting from the current date.
+ let currentDate = Date()
+ for hourOffset in 0 ..< 5 {
+ let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
+ let entry = ArticleEntry(date: entryDate,
+ libArticles: Article.libDocs,
+ destArticles: Article.destDocs)
+ entries.append(entry)
+ }
+
+ let timeline = WidgetKit.Timeline(entries: entries, policy: .atEnd)
+ completion(timeline)
+ }
+
+}
+
+struct ArticleEntry: TimelineEntry {
+ let date: Date
+ let libArticles: [Article]
+ let destArticles: [Article]
+}
+
+struct ArticleWidgetEntryView : View {
+ var entry: ArticleEntry
+
+ @Environment(\.widgetFamily) var widgetFamily
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ ArticleSectionView(sectionTitle: "Library Docs", articles: entry.libArticles)
+ if widgetFamily == .systemLarge {
+ ArticleSectionView(sectionTitle: "Destinations", articles: entry.destArticles)
+ }
+ }
+ .padding(10)
+ }
+}
+
+@main
+struct ArticleWidget: Widget {
+ let kind: String = "ArticleWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: Provider()) { entry in
+ ArticleWidgetEntryView(entry: entry)
+ }
+ .supportedFamilies([.systemMedium, .systemLarge])
+ .configurationDisplayName("Segment Documentation")
+ .description("Documentation at your fingertips.")
+ }
+}
+
+struct ArticleWidget_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ ArticleWidgetEntryView(entry: ArticleEntry(date: Date(),
+ libArticles: Article.libDocs,
+ destArticles: Article.destDocs))
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+ ArticleWidgetEntryView(entry: ArticleEntry(date: Date(),
+ libArticles: Article.libDocs,
+ destArticles: Article.destDocs))
+ .previewContext(WidgetPreviewContext(family: .systemSmall))
+
+ }
+ }
+}
+
+extension Analytics {
+ static var main = Analytics(configuration:
+ Configuration(writeKey: "ABCD")
+ .flushAt(3)
+ .trackApplicationLifecycleEvents(true))
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 00000000..eb878970
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..9221b9bb
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Contents.json b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Segment_logo.imageset/Contents.json b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Segment_logo.imageset/Contents.json
new file mode 100644
index 00000000..69ec13d0
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Segment_logo.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Segment_logo.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Segment_logo.imageset/Segment_logo.png b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Segment_logo.imageset/Segment_logo.png
new file mode 100644
index 00000000..a0e746ee
Binary files /dev/null and b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/Segment_logo.imageset/Segment_logo.png differ
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 00000000..eb878970
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/ArticleWidget/Info.plist b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Info.plist
new file mode 100644
index 00000000..a76958b1
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/ArticleWidget/Info.plist
@@ -0,0 +1,29 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ ArticleWidget
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.pbxproj b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..1ee78cf9
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.pbxproj
@@ -0,0 +1,588 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 52;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ BA1D8C4F26C2D92F00B8185B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1D8C4E26C2D92F00B8185B /* AppDelegate.swift */; };
+ BA1D8C5126C2D92F00B8185B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1D8C5026C2D92F00B8185B /* SceneDelegate.swift */; };
+ BA1D8C5626C2D92F00B8185B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BA1D8C5426C2D92F00B8185B /* Main.storyboard */; };
+ BA1D8C5826C2D93300B8185B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BA1D8C5726C2D93300B8185B /* Assets.xcassets */; };
+ BA1D8C5B26C2D93300B8185B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BA1D8C5926C2D93300B8185B /* LaunchScreen.storyboard */; };
+ BA1D8C6326C2DB7F00B8185B /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1D8C6226C2DB7F00B8185B /* LoginViewController.swift */; };
+ BA1D8C6626C3106D00B8185B /* KeychainItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1D8C6526C3106D00B8185B /* KeychainItem.swift */; };
+ BA1D8C6926C3151600B8185B /* ResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1D8C6826C3151600B8185B /* ResultViewController.swift */; };
+ BAA1FCCA26C98E580022822C /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA1D8C7326C4814B00B8185B /* WidgetKit.framework */; };
+ BAA1FCCB26C98E580022822C /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA1D8C7526C4814B00B8185B /* SwiftUI.framework */; };
+ BAA1FCCE26C98E580022822C /* ArticleWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA1FCCD26C98E580022822C /* ArticleWidget.swift */; };
+ BAA1FCD026C98E5B0022822C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BAA1FCCF26C98E5B0022822C /* Assets.xcassets */; };
+ BAA1FCD426C98E5B0022822C /* ArticleWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = BAA1FCC926C98E580022822C /* ArticleWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ BAA1FCDA26C9957A0022822C /* Article.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA1FCD826C98ED80022822C /* Article.swift */; };
+ BAA1FCDF26C996EF0022822C /* ArticleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA1FCDD26C996CD0022822C /* ArticleItemView.swift */; };
+ BAA1FCE226C9983F0022822C /* ArticleSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA1FCE026C998280022822C /* ArticleSectionView.swift */; };
+ BAC3990026CAF7FB00B6915B /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = BAC398FF26CAF7FB00B6915B /* Segment */; };
+ BAC3990226CAF81200B6915B /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = BAC3990126CAF81200B6915B /* Segment */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ BAA1FCD226C98E5B0022822C /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = BA1D8C4326C2D92F00B8185B /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = BAA1FCC826C98E580022822C;
+ remoteInfo = ArticleWidgetExtension;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ BAA1FCC426C98B460022822C /* Embed App Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ BAA1FCD426C98E5B0022822C /* ArticleWidgetExtension.appex in Embed App Extensions */,
+ );
+ name = "Embed App Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ BA1D8C4B26C2D92F00B8185B /* SegmentExtensionsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SegmentExtensionsExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ BA1D8C4E26C2D92F00B8185B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ BA1D8C5026C2D92F00B8185B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
+ BA1D8C5526C2D92F00B8185B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ BA1D8C5726C2D93300B8185B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ BA1D8C5A26C2D93300B8185B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ BA1D8C5C26C2D93300B8185B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BA1D8C6226C2DB7F00B8185B /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; };
+ BA1D8C6426C30DE100B8185B /* SegmentExtensionsExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SegmentExtensionsExample.entitlements; sourceTree = ""; };
+ BA1D8C6526C3106D00B8185B /* KeychainItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainItem.swift; sourceTree = ""; };
+ BA1D8C6826C3151600B8185B /* ResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultViewController.swift; sourceTree = ""; };
+ BA1D8C7326C4814B00B8185B /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
+ BA1D8C7526C4814B00B8185B /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ BAA1FCC926C98E580022822C /* ArticleWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ArticleWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ BAA1FCCD26C98E580022822C /* ArticleWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleWidget.swift; sourceTree = ""; };
+ BAA1FCCF26C98E5B0022822C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ BAA1FCD126C98E5B0022822C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BAA1FCD826C98ED80022822C /* Article.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Article.swift; sourceTree = ""; };
+ BAA1FCDD26C996CD0022822C /* ArticleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleItemView.swift; sourceTree = ""; };
+ BAA1FCE026C998280022822C /* ArticleSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSectionView.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ BA1D8C4826C2D92F00B8185B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BAC3990026CAF7FB00B6915B /* Segment in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ BAA1FCC626C98E580022822C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BAC3990226CAF81200B6915B /* Segment in Frameworks */,
+ BAA1FCCB26C98E580022822C /* SwiftUI.framework in Frameworks */,
+ BAA1FCCA26C98E580022822C /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ BA1D8C4226C2D92F00B8185B = {
+ isa = PBXGroup;
+ children = (
+ BA1D8C6726C3109300B8185B /* Supporting Files */,
+ BA1D8C4D26C2D92F00B8185B /* SegmentExtensionsExample */,
+ BAA1FCCC26C98E580022822C /* ArticleWidget */,
+ BA1D8C7226C4814B00B8185B /* Frameworks */,
+ BA1D8C4C26C2D92F00B8185B /* Products */,
+ );
+ sourceTree = "";
+ };
+ BA1D8C4C26C2D92F00B8185B /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ BA1D8C4B26C2D92F00B8185B /* SegmentExtensionsExample.app */,
+ BAA1FCC926C98E580022822C /* ArticleWidgetExtension.appex */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ BA1D8C4D26C2D92F00B8185B /* SegmentExtensionsExample */ = {
+ isa = PBXGroup;
+ children = (
+ BA1D8C6426C30DE100B8185B /* SegmentExtensionsExample.entitlements */,
+ BA1D8C4E26C2D92F00B8185B /* AppDelegate.swift */,
+ BA1D8C5026C2D92F00B8185B /* SceneDelegate.swift */,
+ BA1D8C5426C2D92F00B8185B /* Main.storyboard */,
+ BA1D8C6226C2DB7F00B8185B /* LoginViewController.swift */,
+ BA1D8C6826C3151600B8185B /* ResultViewController.swift */,
+ BA1D8C5726C2D93300B8185B /* Assets.xcassets */,
+ BA1D8C5926C2D93300B8185B /* LaunchScreen.storyboard */,
+ BA1D8C5C26C2D93300B8185B /* Info.plist */,
+ );
+ path = SegmentExtensionsExample;
+ sourceTree = "";
+ };
+ BA1D8C6726C3109300B8185B /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ BA1D8C6526C3106D00B8185B /* KeychainItem.swift */,
+ );
+ path = "Supporting Files";
+ sourceTree = "";
+ };
+ BA1D8C7226C4814B00B8185B /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ BA1D8C7326C4814B00B8185B /* WidgetKit.framework */,
+ BA1D8C7526C4814B00B8185B /* SwiftUI.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ BAA1FCCC26C98E580022822C /* ArticleWidget */ = {
+ isa = PBXGroup;
+ children = (
+ BAA1FCCD26C98E580022822C /* ArticleWidget.swift */,
+ BAA1FCDD26C996CD0022822C /* ArticleItemView.swift */,
+ BAA1FCE026C998280022822C /* ArticleSectionView.swift */,
+ BAA1FCD826C98ED80022822C /* Article.swift */,
+ BAA1FCCF26C98E5B0022822C /* Assets.xcassets */,
+ BAA1FCD126C98E5B0022822C /* Info.plist */,
+ );
+ path = ArticleWidget;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ BA1D8C4A26C2D92F00B8185B /* SegmentExtensionsExample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = BA1D8C5F26C2D93300B8185B /* Build configuration list for PBXNativeTarget "SegmentExtensionsExample" */;
+ buildPhases = (
+ BA1D8C4726C2D92F00B8185B /* Sources */,
+ BA1D8C4826C2D92F00B8185B /* Frameworks */,
+ BA1D8C4926C2D92F00B8185B /* Resources */,
+ BAA1FCC426C98B460022822C /* Embed App Extensions */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ BAA1FCD326C98E5B0022822C /* PBXTargetDependency */,
+ );
+ name = SegmentExtensionsExample;
+ packageProductDependencies = (
+ BAC398FF26CAF7FB00B6915B /* Segment */,
+ );
+ productName = SegmentExtensionsExample;
+ productReference = BA1D8C4B26C2D92F00B8185B /* SegmentExtensionsExample.app */;
+ productType = "com.apple.product-type.application";
+ };
+ BAA1FCC826C98E580022822C /* ArticleWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = BAA1FCD526C98E5B0022822C /* Build configuration list for PBXNativeTarget "ArticleWidgetExtension" */;
+ buildPhases = (
+ BAA1FCC526C98E580022822C /* Sources */,
+ BAA1FCC626C98E580022822C /* Frameworks */,
+ BAA1FCC726C98E580022822C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ BAA1FCE426C99CD30022822C /* PBXTargetDependency */,
+ );
+ name = ArticleWidgetExtension;
+ packageProductDependencies = (
+ BAC3990126CAF81200B6915B /* Segment */,
+ );
+ productName = ArticleWidgetExtension;
+ productReference = BAA1FCC926C98E580022822C /* ArticleWidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ BA1D8C4326C2D92F00B8185B /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1250;
+ LastUpgradeCheck = 1250;
+ TargetAttributes = {
+ BA1D8C4A26C2D92F00B8185B = {
+ CreatedOnToolsVersion = 12.5.1;
+ };
+ BAA1FCC826C98E580022822C = {
+ CreatedOnToolsVersion = 12.5.1;
+ };
+ };
+ };
+ buildConfigurationList = BA1D8C4626C2D92F00B8185B /* Build configuration list for PBXProject "SegmentExtensionsExample" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = BA1D8C4226C2D92F00B8185B;
+ packageReferences = (
+ );
+ productRefGroup = BA1D8C4C26C2D92F00B8185B /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ BA1D8C4A26C2D92F00B8185B /* SegmentExtensionsExample */,
+ BAA1FCC826C98E580022822C /* ArticleWidgetExtension */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ BA1D8C4926C2D92F00B8185B /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BA1D8C5B26C2D93300B8185B /* LaunchScreen.storyboard in Resources */,
+ BA1D8C5826C2D93300B8185B /* Assets.xcassets in Resources */,
+ BA1D8C5626C2D92F00B8185B /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ BAA1FCC726C98E580022822C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BAA1FCD026C98E5B0022822C /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ BA1D8C4726C2D92F00B8185B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BA1D8C6926C3151600B8185B /* ResultViewController.swift in Sources */,
+ BA1D8C6626C3106D00B8185B /* KeychainItem.swift in Sources */,
+ BA1D8C6326C2DB7F00B8185B /* LoginViewController.swift in Sources */,
+ BA1D8C4F26C2D92F00B8185B /* AppDelegate.swift in Sources */,
+ BA1D8C5126C2D92F00B8185B /* SceneDelegate.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ BAA1FCC526C98E580022822C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BAA1FCDA26C9957A0022822C /* Article.swift in Sources */,
+ BAA1FCCE26C98E580022822C /* ArticleWidget.swift in Sources */,
+ BAA1FCDF26C996EF0022822C /* ArticleItemView.swift in Sources */,
+ BAA1FCE226C9983F0022822C /* ArticleSectionView.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ BAA1FCD326C98E5B0022822C /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = BAA1FCC826C98E580022822C /* ArticleWidgetExtension */;
+ targetProxy = BAA1FCD226C98E5B0022822C /* PBXContainerItemProxy */;
+ };
+ BAA1FCE426C99CD30022822C /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ productRef = BAA1FCE326C99CD30022822C /* Segment */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ BA1D8C5426C2D92F00B8185B /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ BA1D8C5526C2D92F00B8185B /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ BA1D8C5926C2D93300B8185B /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ BA1D8C5A26C2D93300B8185B /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ BA1D8C5D26C2D93300B8185B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.5;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ BA1D8C5E26C2D93300B8185B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.5;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ BA1D8C6026C2D93300B8185B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = SegmentExtensionsExample/SegmentExtensionsExample.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = V8R4668S2H;
+ INFOPLIST_FILE = SegmentExtensionsExample/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = co.alancharles.SegmentExtensionsExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ BA1D8C6126C2D93300B8185B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = SegmentExtensionsExample/SegmentExtensionsExample.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = V8R4668S2H;
+ INFOPLIST_FILE = SegmentExtensionsExample/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = co.alancharles.SegmentExtensionsExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ BAA1FCD626C98E5B0022822C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = V8R4668S2H;
+ INFOPLIST_FILE = ArticleWidget/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = co.alancharles.SegmentExtensionsExample.ArticleWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ BAA1FCD726C98E5B0022822C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = V8R4668S2H;
+ INFOPLIST_FILE = ArticleWidget/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = co.alancharles.SegmentExtensionsExample.ArticleWidget;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ BA1D8C4626C2D92F00B8185B /* Build configuration list for PBXProject "SegmentExtensionsExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BA1D8C5D26C2D93300B8185B /* Debug */,
+ BA1D8C5E26C2D93300B8185B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ BA1D8C5F26C2D93300B8185B /* Build configuration list for PBXNativeTarget "SegmentExtensionsExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BA1D8C6026C2D93300B8185B /* Debug */,
+ BA1D8C6126C2D93300B8185B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ BAA1FCD526C98E5B0022822C /* Build configuration list for PBXNativeTarget "ArticleWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BAA1FCD626C98E5B0022822C /* Debug */,
+ BAA1FCD726C98E5B0022822C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ BA1D8C6A26C4750F00B8185B /* XCRemoteSwiftPackageReference "analytics-swift" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "git@github.com:segmentio/analytics-swift.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 1.0.2;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ BAA1FCE326C99CD30022822C /* Segment */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = BA1D8C6A26C4750F00B8185B /* XCRemoteSwiftPackageReference "analytics-swift" */;
+ productName = Segment;
+ };
+ BAC398FF26CAF7FB00B6915B /* Segment */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Segment;
+ };
+ BAC3990126CAF81200B6915B /* Segment */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Segment;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = BA1D8C4326C2D92F00B8185B /* Project object */;
+}
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/xcshareddata/xcschemes/SegmentExtensionsExample.xcscheme b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/xcshareddata/xcschemes/SegmentExtensionsExample.xcscheme
new file mode 100644
index 00000000..73fdf0a4
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcodeproj/xcshareddata/xcschemes/SegmentExtensionsExample.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcworkspace/contents.xcworkspacedata b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..e9c803ea
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/AppDelegate.swift b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/AppDelegate.swift
new file mode 100644
index 00000000..002476e2
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/AppDelegate.swift
@@ -0,0 +1,50 @@
+//
+// AppDelegate.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/10/21.
+//
+
+import UIKit
+import Segment
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var analytics: Analytics? = nil
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+
+ let configuration = Configuration(writeKey: "ABCD")
+ .trackApplicationLifecycleEvents(true)
+ .flushInterval(1)
+
+ analytics = Analytics(configuration: configuration)
+
+ return true
+ }
+
+ // MARK: UISceneSession Lifecycle
+
+ func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+ // Called when a new scene session is being created.
+ // Use this method to select a configuration to create the new scene with.
+ return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+ }
+
+ func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
+ // Called when the user discards a scene session.
+ // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
+ // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ }
+}
+
+extension UIApplicationDelegate {
+ var analytics: Analytics? {
+ if let appDelegate = self as? AppDelegate {
+ return appDelegate.analytics
+ }
+ return nil
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/AccentColor.colorset/Contents.json b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 00000000..eb878970
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..9221b9bb
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Contents.json b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git "a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Segment\342\200\224Avatar\342\200\224Green.imageset/Contents.json" "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Segment\342\200\224Avatar\342\200\224Green.imageset/Contents.json"
new file mode 100644
index 00000000..d16a9445
--- /dev/null
+++ "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Segment\342\200\224Avatar\342\200\224Green.imageset/Contents.json"
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "Segment—Avatar—Green@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git "a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Segment\342\200\224Avatar\342\200\224Green.imageset/Segment\342\200\224Avatar\342\200\224Green@2x.png" "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Segment\342\200\224Avatar\342\200\224Green.imageset/Segment\342\200\224Avatar\342\200\224Green@2x.png"
new file mode 100644
index 00000000..afc2dfb9
Binary files /dev/null and "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Segment\342\200\224Avatar\342\200\224Green.imageset/Segment\342\200\224Avatar\342\200\224Green@2x.png" differ
diff --git "a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.imageset/Contents.json" "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.imageset/Contents.json"
new file mode 100644
index 00000000..01e839af
--- /dev/null
+++ "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.imageset/Contents.json"
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Twilio—Segment—Horizontal—Green.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git "a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.imageset/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.png" "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.imageset/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.png"
new file mode 100644
index 00000000..d8d5b730
Binary files /dev/null and "b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Assets.xcassets/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.imageset/Twilio\342\200\224Segment\342\200\224Horizontal\342\200\224Green.png" differ
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Base.lproj/LaunchScreen.storyboard b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..865e9329
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Base.lproj/Main.storyboard b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..ce835ac5
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Base.lproj/Main.storyboard
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Info.plist b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Info.plist
new file mode 100644
index 00000000..4c850baa
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/Info.plist
@@ -0,0 +1,73 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneConfigurationName
+ Default Configuration
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).SceneDelegate
+ UISceneStoryboardFile
+ Main
+
+
+
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~iphone
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift
new file mode 100644
index 00000000..228e42d6
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift
@@ -0,0 +1,165 @@
+//
+// LoginViewController.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/10/21.
+//
+
+import UIKit
+import AuthenticationServices
+
+class LoginViewController: UIViewController {
+
+ var analytics = UIApplication.shared.delegate?.analytics
+
+ @IBOutlet weak var loginProviderStackView: UIStackView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupProviderLoginView()
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ performExistingAccountSetupFlows()
+ }
+
+ //appleId button
+ func setupProviderLoginView() {
+ let authorizationButton = ASAuthorizationAppleIDButton()
+ authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
+ self.loginProviderStackView.addArrangedSubview(authorizationButton)
+ }
+
+ //prompts the user if existing login info is found.
+ func performExistingAccountSetupFlows() {
+
+ //prepare requests for Apple ID and password providers
+ let requests = [ASAuthorizationAppleIDProvider().createRequest(),
+ ASAuthorizationPasswordProvider().createRequest()]
+
+ //create and authorization controller with above requests
+ let authorizationController = ASAuthorizationController(authorizationRequests: requests)
+ authorizationController.delegate = self
+ authorizationController.presentationContextProvider = self
+ authorizationController.performRequests()
+ }
+
+ //perform appleId request
+ @objc
+ func handleAuthorizationAppleIDButtonPress() {
+ let appleIDProvider = ASAuthorizationAppleIDProvider()
+ let request = appleIDProvider.createRequest()
+ request.requestedScopes = [.fullName, .email]
+
+ let authorizationController = ASAuthorizationController(authorizationRequests: [request])
+ authorizationController.delegate = self
+ authorizationController.presentationContextProvider = self
+ authorizationController.performRequests()
+
+ analytics?.track(name: "Apple ID Button Pressed")
+ }
+}
+
+//MARK: - ASAuthorizationController Delegate conformance
+extension LoginViewController: ASAuthorizationControllerDelegate {
+ func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
+ switch authorization.credential {
+ case let appleIDCredential as ASAuthorizationAppleIDCredential:
+
+ //struct for identify (better place to put this?)
+ struct UserTraits: Codable {
+ let name: PersonNameComponents?
+ let email: String?
+ var username: String? = nil
+ }
+
+ let userIdentifier = appleIDCredential.user
+ let fullName = appleIDCredential.fullName
+ let email = appleIDCredential.email
+
+ self.saveUserInKeychain(userIdentifier)
+
+ //identify the user
+ analytics?.identify(userId: userIdentifier, traits: UserTraits(name: fullName, email: email))
+
+ self.showResultViewController(userIdentifier: userIdentifier, fullName: fullName, email: email)
+
+ case let passwordCredential as ASPasswordCredential:
+
+ let username = passwordCredential.user
+ let password = passwordCredential.password
+
+ DispatchQueue.main.async {
+ self.showPasswordCredentialAlert(username: username, password: password)
+ }
+
+ default:
+ break
+ }
+ }
+
+ private func saveUserInKeychain(_ userIdentifier: String) {
+
+ //change 'co.alancharles.SegmentExtensionsExample' to your identifier
+ do {
+ try KeychainItem(service: "co.alancharles.SegmentExtensionsExample", account: "userIdentifier").saveItem(userIdentifier)
+ analytics?.track(name: "Saved to Keychain")
+ } catch {
+ //handle error and optionally track it
+ print("Unable to save userId to keychain.")
+ }
+ }
+
+ private func showResultViewController(userIdentifier: String, fullName: PersonNameComponents?, email: String?){
+ guard let viewController = self.presentingViewController as? ResultViewController
+ else { return }
+
+ DispatchQueue.main.async {
+ viewController.userIdentifierLabel.text = userIdentifier
+ if let givenName = fullName?.givenName {
+ viewController.givenNameLabel.text = givenName
+ }
+ if let familyName = fullName?.familyName {
+ viewController.familyNameLabel.text = familyName
+ }
+ if let email = email {
+ viewController.emailLabel.text = email
+ }
+ self.dismiss(animated: true, completion: nil)
+ }
+ }
+
+ private func showPasswordCredentialAlert(username: String, password: String) {
+ let message = "This app has recieved your selected credential from the keychain.\n\n Username: \(username)\n Password: \(password)"
+ let alertController = UIAlertController(title: "Keychain Credential Received", message: message, preferredStyle: .alert)
+
+ alertController.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil))
+ self.present(alertController, animated: true, completion: nil)
+ }
+
+ func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
+ //handle error
+ print(error)
+ }
+}
+
+//MARK: - ASAuthorizationController Delegate presentation conformance
+extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
+ func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
+ return self.view.window!
+ }
+}
+
+extension UIViewController {
+
+ func showLoginViewController() {
+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
+ if let loginViewController = storyboard.instantiateViewController(identifier: "loginViewController")
+ as? LoginViewController {
+ loginViewController.modalPresentationStyle = .formSheet
+ loginViewController.isModalInPresentation = true
+ self.present(loginViewController, animated: true, completion: nil)
+ }
+ }
+}
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/ResultViewController.swift b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/ResultViewController.swift
new file mode 100644
index 00000000..aca2ffd9
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/ResultViewController.swift
@@ -0,0 +1,40 @@
+//
+// ResultViewController.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/10/21.
+//
+
+import UIKit
+import AuthenticationServices
+
+class ResultViewController: UIViewController {
+ var analytics = UIApplication.shared.delegate?.analytics
+
+ @IBOutlet weak var userIdentifierLabel: UILabel!
+ @IBOutlet weak var givenNameLabel: UILabel!
+ @IBOutlet weak var familyNameLabel: UILabel!
+ @IBOutlet weak var emailLabel: UILabel!
+ @IBOutlet weak var signOutButton: UIButton!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ userIdentifierLabel.text = KeychainItem.currentUserIdentifier
+ }
+
+ @IBAction func signOutButtonPressed(_ sender: UIButton!) {
+ KeychainItem.deleteUserIdentifierFromKeychain()
+
+ userIdentifierLabel.text = ""
+ givenNameLabel.text = ""
+ familyNameLabel.text = ""
+ emailLabel.text = ""
+
+ DispatchQueue.main.async {
+ self.showLoginViewController()
+ }
+
+ analytics?.track(name: "Logged Out")
+ }
+}
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/SceneDelegate.swift b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/SceneDelegate.swift
new file mode 100644
index 00000000..7e36606b
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/SceneDelegate.swift
@@ -0,0 +1,52 @@
+//
+// SceneDelegate.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/10/21.
+//
+
+import UIKit
+
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+ var window: UIWindow?
+
+
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
+ // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
+ // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
+ guard let _ = (scene as? UIWindowScene) else { return }
+ }
+
+ func sceneDidDisconnect(_ scene: UIScene) {
+ // Called as the scene is being released by the system.
+ // This occurs shortly after the scene enters the background, or when its session is discarded.
+ // Release any resources associated with this scene that can be re-created the next time the scene connects.
+ // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
+ }
+
+ func sceneDidBecomeActive(_ scene: UIScene) {
+ // Called when the scene has moved from an inactive state to an active state.
+ // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
+ }
+
+ func sceneWillResignActive(_ scene: UIScene) {
+ // Called when the scene will move from an active state to an inactive state.
+ // This may occur due to temporary interruptions (ex. an incoming phone call).
+ }
+
+ func sceneWillEnterForeground(_ scene: UIScene) {
+ // Called as the scene transitions from the background to the foreground.
+ // Use this method to undo the changes made on entering the background.
+ }
+
+ func sceneDidEnterBackground(_ scene: UIScene) {
+ // Called as the scene transitions from the foreground to the background.
+ // Use this method to save data, release shared resources, and store enough scene-specific state information
+ // to restore the scene back to its current state.
+ }
+
+
+}
+
diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/SegmentExtensionsExample.entitlements b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/SegmentExtensionsExample.entitlements
new file mode 100644
index 00000000..a812db50
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/SegmentExtensionsExample.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.applesignin
+
+ Default
+
+
+
diff --git a/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift b/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift
new file mode 100644
index 00000000..715958a3
--- /dev/null
+++ b/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift
@@ -0,0 +1,149 @@
+//
+// KeychainItem.swift
+// SegmentExtensionsExample
+//
+// Created by Alan Charles on 8/10/21.
+//
+
+import Foundation
+
+struct KeychainItem {
+ // MARK: Types
+
+ enum KeychainError: Error {
+ case noPassword
+ case unexpectedPasswordData
+ case unexpectedItemData
+ case unhandledError
+ }
+
+ // MARK: Properties
+
+ let service: String
+
+ private(set) var account: String
+
+ let accessGroup: String?
+
+ // MARK: Intialization
+
+ init(service: String, account: String, accessGroup: String? = nil) {
+ self.service = service
+ self.account = account
+ self.accessGroup = accessGroup
+ }
+
+ // MARK: Keychain access
+
+ func readItem() throws -> String {
+ /*
+ Build a query to find the item that matches the service, account and
+ access group.
+ */
+ var query = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
+ query[kSecReturnAttributes as String] = kCFBooleanTrue
+ query[kSecReturnData as String] = kCFBooleanTrue
+
+ // Try to fetch the existing keychain item that matches the query.
+ var queryResult: AnyObject?
+ let status = withUnsafeMutablePointer(to: &queryResult) {
+ SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
+ }
+
+ // Check the return status and throw an error if appropriate.
+ guard status != errSecItemNotFound else { throw KeychainError.noPassword }
+ guard status == noErr else { throw KeychainError.unhandledError }
+
+ // Parse the password string from the query result.
+ guard let existingItem = queryResult as? [String: AnyObject],
+ let passwordData = existingItem[kSecValueData as String] as? Data,
+ let password = String(data: passwordData, encoding: String.Encoding.utf8)
+ else {
+ throw KeychainError.unexpectedPasswordData
+ }
+
+ return password
+ }
+
+ func saveItem(_ password: String) throws {
+ // Encode the password into an Data object.
+ let encodedPassword = password.data(using: String.Encoding.utf8)!
+
+ do {
+ // Check for an existing item in the keychain.
+ try _ = readItem()
+
+ // Update the existing item with the new password.
+ var attributesToUpdate = [String: AnyObject]()
+ attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject?
+
+ let query = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
+ let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
+
+ // Throw an error if an unexpected status was returned.
+ guard status == noErr else { throw KeychainError.unhandledError }
+ } catch KeychainError.noPassword {
+ /*
+ No password was found in the keychain. Create a dictionary to save
+ as a new keychain item.
+ */
+ var newItem = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
+ newItem[kSecValueData as String] = encodedPassword as AnyObject?
+
+ // Add a the new item to the keychain.
+ let status = SecItemAdd(newItem as CFDictionary, nil)
+
+ // Throw an error if an unexpected status was returned.
+ guard status == noErr else { throw KeychainError.unhandledError }
+ }
+ }
+
+ func deleteItem() throws {
+ // Delete the existing item from the keychain.
+ let query = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
+ let status = SecItemDelete(query as CFDictionary)
+
+ // Throw an error if an unexpected status was returned.
+ guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError }
+ }
+
+ // MARK: Convenience
+
+ private static func keychainQuery(withService service: String, account: String? = nil, accessGroup: String? = nil) -> [String: AnyObject] {
+ var query = [String: AnyObject]()
+ query[kSecClass as String] = kSecClassGenericPassword
+ query[kSecAttrService as String] = service as AnyObject?
+
+ if let account = account {
+ query[kSecAttrAccount as String] = account as AnyObject?
+ }
+
+ if let accessGroup = accessGroup {
+ query[kSecAttrAccessGroup as String] = accessGroup as AnyObject?
+ }
+
+ return query
+ }
+
+ /*
+ For the purpose of this demo app, the user identifier will be stored in the device keychain.
+ You should store the user identifier in your account management system.
+ */
+ static var currentUserIdentifier: String {
+ do {
+ let storedIdentifier = try KeychainItem(service: "co.alancharles.SegmentExtensionsExample", account: "userIdentifier").readItem()
+ return storedIdentifier
+ } catch {
+ return ""
+ }
+ }
+
+ static func deleteUserIdentifierFromKeychain() {
+ do {
+ try KeychainItem(service: "co.alancharles.SegmentExtensionsExample", account: "userIdentifier").deleteItem()
+ } catch {
+ print("Unable to delete userIdentifier from keychain")
+ }
+ }
+}