diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme
new file mode 100644
index 0000000..b98877e
--- /dev/null
+++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example-macOS.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Example/Example/App.swift b/Example/Example/App.swift
new file mode 100644
index 0000000..55e8f7b
--- /dev/null
+++ b/Example/Example/App.swift
@@ -0,0 +1,93 @@
+import SwiftUI
+import WhatsNewKit
+
+// MARK: - App
+
+/// The App
+@main
+struct App {}
+
+// MARK: - SwiftUI.App
+
+extension App: SwiftUI.App {
+
+ /// The content and behavior of the app.
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ .environment(
+ \.whatsNew,
+ .init(
+ versionStore: InMemoryWhatsNewVersionStore(),
+ whatsNewCollection: self
+ )
+ )
+ }
+ }
+
+}
+
+// MARK: - App+WhatsNewCollectionProvider
+
+extension App: WhatsNewCollectionProvider {
+
+ /// A WhatsNewCollection
+ var whatsNewCollection: WhatsNewCollection {
+ WhatsNew(
+ version: "1.0.0",
+ title: "WhatsNewKit",
+ features: [
+ .init(
+ image: .init(
+ systemName: "star.fill",
+ foregroundColor: .orange
+ ),
+ title: "Showcase your new App Features",
+ subtitle: "Present your new app features just like a native app from Apple."
+ ),
+ .init(
+ image: .init(
+ systemName: "wand.and.stars",
+ foregroundColor: .cyan
+ ),
+ title: "Automatic Presentation",
+ subtitle: .init(
+ try! AttributedString(
+ markdown: "Simply declare a WhatsNew per Version and present it automatically by using the `.whatsNewSheet()` modifier."
+ )
+ )
+ ),
+ .init(
+ image: .init(
+ systemName: "gear.circle.fill",
+ foregroundColor: .gray
+ ),
+ title: "Configuration",
+ subtitle: "Easily adjust colors, strings, haptic feedback, behaviours and the layout of the presented WhatsNewView to your needs."
+ ),
+ .init(
+ image: .init(
+ systemName: "swift",
+ foregroundColor: .init(.init(red: 240.0 / 255, green: 81.0 / 255, blue: 56.0 / 255, alpha: 1))
+ ),
+ title: "Swift Package Manager",
+ subtitle: "WhatsNewKit can be easily integrated via the Swift Package Manager."
+ )
+ ],
+ primaryAction: .init(
+ hapticFeedback: {
+ #if os(iOS)
+ .notification(.success)
+ #else
+ nil
+ #endif
+ }()
+ ),
+ secondaryAction: .init(
+ title: "Learn more",
+ action: .openURL(.init(string: "https://github.com/SvenTiigi/WhatsNewKit"))
+ )
+ )
+ }
+
+}
diff --git a/Example/Example/ContentView.swift b/Example/Example/ContentView.swift
new file mode 100644
index 0000000..80bc9dc
--- /dev/null
+++ b/Example/Example/ContentView.swift
@@ -0,0 +1,24 @@
+import SwiftUI
+import WhatsNewKit
+
+// MARK: - ContentView
+
+/// The ContentView
+struct ContentView {}
+
+// MARK: - View
+
+extension ContentView: View {
+
+ /// The content and behavior of the view
+ var body: some View {
+ NavigationView {
+ ExamplesView()
+ }
+ #if !os(macOS)
+ .navigationViewStyle(.stack)
+ #endif
+ .whatsNewSheet()
+ }
+
+}
diff --git a/Example/Example/ExamplesView.swift b/Example/Example/ExamplesView.swift
new file mode 100644
index 0000000..60483db
--- /dev/null
+++ b/Example/Example/ExamplesView.swift
@@ -0,0 +1,238 @@
+import SwiftUI
+import WhatsNewKit
+
+// MARK: - ExamplesView
+
+/// The ExamplesView
+struct ExamplesView {
+
+ /// The Examples
+ private let examples = WhatsNew.Example.allCases
+
+ /// The currently presented WhatsNew object
+ @State
+ private var whatsNew: WhatsNew?
+
+}
+
+// MARK: - View
+
+extension ExamplesView: View {
+
+ /// The content and behavior of the view
+ var body: some View {
+ List {
+ Section(
+ header: Text(
+ verbatim: "Examples"
+ ),
+ footer: Text(
+ verbatim: "Tap on an example to manually present a WhatsNewView"
+ )
+ ) {
+ ForEach(
+ self.examples,
+ id: \.rawValue
+ ) { example in
+ Button(
+ action: {
+ self.whatsNew = example.whatsNew
+ }
+ ) {
+ Text(
+ verbatim: example.displayName
+ )
+ }
+ }
+ }
+ }
+ .navigationTitle("WhatsNewKit")
+ .toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Link(
+ destination: .init(
+ string: "https://github.com/SvenTiigi/WhatsNewKit"
+ )!
+ ) {
+ Image(systemName: "text.book.closed.fill")
+ }
+ }
+ }
+ .sheet(
+ whatsNew: self.$whatsNew
+ )
+ }
+
+}
+
+// MARK: - WhatsNew+Example
+
+private extension WhatsNew {
+
+ /// A WhatsNew Example
+ enum Example: String, Codable, Hashable, CaseIterable {
+ /// Calendar
+ case calendar
+ /// Maps
+ case maps
+ /// Translate
+ case translate
+ }
+
+}
+
+// MARK: - WhatsNew+Example+displayName
+
+private extension WhatsNew.Example {
+
+ /// The user friendly display name
+ var displayName: String {
+ self.rawValue.prefix(1).capitalized + self.rawValue.dropFirst()
+ }
+
+}
+
+// MARK: - WhatsNew+Example+whatsNew
+
+private extension WhatsNew.Example {
+
+ /// The WhatsNew
+ var whatsNew: WhatsNew {
+ switch self {
+ case .calendar:
+ return .init(
+ title: "What's New in Calendar",
+ features: [
+ .init(
+ image: .init(
+ systemName: "envelope",
+ foregroundColor: .red
+ ),
+ title: "Found Events",
+ subtitle: "Siri suggests events found in Mail, Messages, and Safari, so you can add them easily, such as flight reservations and hotel bookings."
+ ),
+ .init(
+ image: .init(
+ systemName: "clock",
+ foregroundColor: .red
+ ),
+ title: "Time to Leave",
+ subtitle: "Calendar uses Apple Maps to look up locations, traffic conditions, and transit options to tell you when it's time to leave."
+ ),
+ .init(
+ image: .init(
+ systemName: "location",
+ foregroundColor: .red
+ ),
+ title: "Location Suggestions",
+ subtitle: "Calendar suggests locations based on your past events and significant locations."
+ )
+ ],
+ primaryAction: .init(
+ backgroundColor: .red
+ )
+ )
+ case .maps:
+ return .init(
+ title: "What's New in Maps",
+ features: [
+ .init(
+ image: .init(
+ systemName: "map.fill",
+ foregroundColor: .green
+ ),
+ title: "Updated Map Style",
+ subtitle: "An improved design makes it easier to navigate and explore the map."
+ ),
+ .init(
+ image: .init(
+ systemName: "mappin.and.ellipse",
+ foregroundColor: .pink
+ ),
+ title: "All-New Place Cards",
+ subtitle: "Completely redesigned place cards make it easier to learn about and interact with places."
+ ),
+ .init(
+ image: .init(
+ systemName: "magnifyingglass",
+ foregroundColor: .blue
+ ),
+ title: "Improved Search",
+ subtitle: "Finding places is now easier with filters and automatic updates when you're browsing results on the map."
+ )
+ ],
+ primaryAction: .init(backgroundColor: .blue),
+ secondaryAction: .init(
+ title: "About Apple Maps & Privacy",
+ foregroundColor: .blue,
+ action: .openURL(.init(string: "maps://"))
+ )
+ )
+ case .translate:
+ return .init(
+ title: .init(
+ text: .init(
+ "What's New in "
+ + AttributedString(
+ "Translate",
+ attributes: .foregroundColor(.cyan)
+ )
+ )
+ ),
+ features: [
+ .init(
+ image: .init(
+ systemName: "rectangle.portrait.bottomthird.inset.filled",
+ foregroundColor: .cyan
+ ),
+ title: "Conversation Views",
+ subtitle: "Choose a side-by-side or face-to-face conversation view."
+ ),
+ .init(
+ image: .init(
+ systemName: "mic",
+ foregroundColor: .cyan
+ ),
+ title: "Auto Translate",
+ subtitle: "Respond in conversations without tapping the microphone button."
+ ),
+ .init(
+ image: .init(
+ systemName: "iphone",
+ foregroundColor: .cyan
+ ),
+ title: "System-Wide Translation",
+ subtitle: "Translate selected text anywhere on your iPhone."
+ )
+ ],
+ primaryAction: .init(
+ backgroundColor: .cyan
+ ),
+ secondaryAction: .init(
+ title: "About Translation & Privacy",
+ foregroundColor: .cyan,
+ action: .openURL(
+ .init(string: "https://apple.com/privacy")
+ )
+ )
+ )
+ }
+ }
+
+}
+
+// MARK: - AttributeContainer+foregroundColor
+
+private extension AttributeContainer {
+
+ /// A AttributeContainer with a given foreground color
+ /// - Parameter color: The foreground color
+ static func foregroundColor(
+ _ color: Color
+ ) -> Self {
+ var container = Self()
+ container.foregroundColor = color
+ return container
+ }
+
+}
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AccentColor.colorset/Contents.json b/Example/Example/Resources/Assets-iOS.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..6112869
--- /dev/null
+++ b/Example/Example/Resources/Assets-iOS.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "platform" : "universal",
+ "reference" : "systemBlueColor"
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-1024px.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-1024px.png
new file mode 100644
index 0000000..6b5ac9e
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-1024px.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-120px-40pt@3x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-120px-40pt@3x.png
new file mode 100644
index 0000000..864402c
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-120px-40pt@3x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-120px-60pt@2x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-120px-60pt@2x.png
new file mode 100644
index 0000000..864402c
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-120px-60pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-152px-76pt@2x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-152px-76pt@2x.png
new file mode 100644
index 0000000..6531a4f
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-152px-76pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-167px-83.5pt@2x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-167px-83.5pt@2x.png
new file mode 100644
index 0000000..c1b6b08
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-167px-83.5pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-180px-60pt@3x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-180px-60pt@3x.png
new file mode 100644
index 0000000..fad5d5f
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-180px-60pt@3x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-20px-20pt@1x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-20px-20pt@1x.png
new file mode 100644
index 0000000..079bac1
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-20px-20pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-29px-29pt@1x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-29px-29pt@1x.png
new file mode 100644
index 0000000..db6c2b7
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-29px-29pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-40px-20pt@2x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-40px-20pt@2x.png
new file mode 100644
index 0000000..127e765
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-40px-20pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-40px-40pt@1x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-40px-40pt@1x.png
new file mode 100644
index 0000000..127e765
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-40px-40pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-58px-29pt@2x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-58px-29pt@2x.png
new file mode 100644
index 0000000..425373a
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-58px-29pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-60px-20pt@3x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-60px-20pt@3x.png
new file mode 100644
index 0000000..423558f
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-60px-20pt@3x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-76px-76pt@1x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-76px-76pt@1x.png
new file mode 100644
index 0000000..50b8992
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-76px-76pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-80px-40pt@2x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-80px-40pt@2x.png
new file mode 100644
index 0000000..9e5056d
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-80px-40pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-87px-29pt@3x.png b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-87px-29pt@3x.png
new file mode 100644
index 0000000..6fdd336
Binary files /dev/null and b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/AppIcon-87px-29pt@3x.png differ
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/Contents.json b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/Contents.json
new file mode 100644
index 0000000..4ac3ec4
--- /dev/null
+++ b/Example/Example/Resources/Assets-iOS.xcassets/AppIcon-iOS.appiconset/Contents.json
@@ -0,0 +1,116 @@
+{
+ "images" : [
+ {
+ "filename" : "AppIcon-40px-20pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-60px-20pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-58px-29pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-87px-29pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-80px-40pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-120px-40pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-120px-60pt@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-180px-60pt@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-20px-20pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-40px-20pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29px-29pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-58px-29pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40px-40pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-80px-40pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-76px-76pt@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-152px-76pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-167px-83.5pt@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "AppIcon-1024px.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/Assets-iOS.xcassets/Contents.json b/Example/Example/Resources/Assets-iOS.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example/Resources/Assets-iOS.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AccentColor.colorset/Contents.json b/Example/Example/Resources/Assets-macOS.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..6112869
--- /dev/null
+++ b/Example/Example/Resources/Assets-macOS.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "platform" : "universal",
+ "reference" : "systemBlueColor"
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/Contents.json b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/Contents.json
new file mode 100644
index 0000000..f93a293
--- /dev/null
+++ b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "filename" : "macOS-AppIcon-16px-16pt@1x.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "macOS-AppIcon-32px-16pt@2x.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "filename" : "macOS-AppIcon-32px-32pt@1x.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "macOS-AppIcon-64px-32pt@2x.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "filename" : "macOS-AppIcon-128px-128pt@1x.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "macOS-AppIcon-256px-128pt@2x.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "filename" : "macOS-AppIcon-256px-256pt@1x.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "macOS-AppIcon-512px-256pt@2x.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "filename" : "macOS-AppIcon-512px.png",
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "filename" : "macOS-AppIcon-1024px.png",
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-1024px.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-1024px.png
new file mode 100644
index 0000000..04395d5
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-1024px.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-128px-128pt@1x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-128px-128pt@1x.png
new file mode 100644
index 0000000..df049a0
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-128px-128pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-16px-16pt@1x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-16px-16pt@1x.png
new file mode 100644
index 0000000..adb6dc9
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-16px-16pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-256px-128pt@2x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-256px-128pt@2x.png
new file mode 100644
index 0000000..52c3314
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-256px-128pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-256px-256pt@1x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-256px-256pt@1x.png
new file mode 100644
index 0000000..52c3314
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-256px-256pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-32px-16pt@2x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-32px-16pt@2x.png
new file mode 100644
index 0000000..71577b7
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-32px-16pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-32px-32pt@1x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-32px-32pt@1x.png
new file mode 100644
index 0000000..71577b7
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-32px-32pt@1x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-512px-256pt@2x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-512px-256pt@2x.png
new file mode 100644
index 0000000..8a211e2
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-512px-256pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-512px.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-512px.png
new file mode 100644
index 0000000..8a211e2
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-512px.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-64px-32pt@2x.png b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-64px-32pt@2x.png
new file mode 100644
index 0000000..cbcc2c8
Binary files /dev/null and b/Example/Example/Resources/Assets-macOS.xcassets/AppIcon-macOS.appiconset/macOS-AppIcon-64px-32pt@2x.png differ
diff --git a/Example/Example/Resources/Assets-macOS.xcassets/Contents.json b/Example/Example/Resources/Assets-macOS.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Example/Example/Resources/Assets-macOS.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example/Example/Resources/Entitlements.entitlements b/Example/Example/Resources/Entitlements.entitlements
new file mode 100644
index 0000000..f2ef3ae
--- /dev/null
+++ b/Example/Example/Resources/Entitlements.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.files.user-selected.read-only
+
+
+
diff --git a/Example/ExampleViewController/ConfigurationCollectionViewCell.swift b/Example/ExampleViewController/ConfigurationCollectionViewCell.swift
deleted file mode 100644
index abfab05..0000000
--- a/Example/ExampleViewController/ConfigurationCollectionViewCell.swift
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-// ConfigurationCollectionViewCell.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-import WhatsNewKit
-
-/// The ConfigurationCollectionViewCell
-class ConfigurationCollectionViewCell: UICollectionViewCell, ReuseIdentifiable {
-
- // MARK: Properties
-
- /// The TitleLabel
- lazy var titleLabel: UILabel = {
- let label = UILabel()
- label.textColor = .white
- label.textAlignment = .center
- label.numberOfLines = 0
- label.lineBreakMode = .byWordWrapping
- label.font = .systemFont(ofSize: 16)
- return label
- }()
-
- /// Boolean if is selected
- override var isSelected: Bool {
- didSet {
- UIView.animate(
- withDuration: 0.5,
- delay: 0,
- usingSpringWithDamping: 0.9,
- initialSpringVelocity: 0,
- options: .curveLinear,
- animations: { [weak self] in
- if self?.isSelected == true {
- self?.backgroundColor = self?.preferredBackgroundColor
- self?.transform = CGAffineTransform(
- scaleX: 1.09,
- y: 1.07
- )
- } else {
- self?.transform = .identity
- self?.backgroundColor = .mainGray
- }
- }
- )
- }
- }
-
- /// The preferred BackgroundColor
- var preferredBackgroundColor: UIColor {
- switch self.titleLabel.text {
- case "Blue":
- return .main
- case "LightBlue":
- return .whatsNewKitLightBlue
- case "Orange":
- return .orange
- case "Purple":
- return .whatsNewKitPurple
- case "Red":
- return .whatsNewKitRed
- case "Green":
- return .whatsNewKitGreen
- default:
- return .main
- }
- }
-
- // MARK: Initializer
-
- /// Designated Initializer
- ///
- /// - Parameter frame: The Frame
- override init(frame: CGRect) {
- super.init(frame: frame)
- // Add TitleLabel
- self.contentView.addSubview(self.titleLabel)
- // Add Corner Radius
- self.layer.cornerRadius = 10
- // Set default BackgroundColor
- self.backgroundColor = .mainGray
- }
-
- /// Initializer with Coder always return nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- // MARK: View-Lifecycle
-
- /// Layout Subviews
- override func layoutSubviews() {
- super.layoutSubviews()
- self.titleLabel.frame = self.contentView.bounds
- }
-
-}
diff --git a/Example/ExampleViewController/ConfigurationsTableViewCell.swift b/Example/ExampleViewController/ConfigurationsTableViewCell.swift
deleted file mode 100644
index f2b979e..0000000
--- a/Example/ExampleViewController/ConfigurationsTableViewCell.swift
+++ /dev/null
@@ -1,146 +0,0 @@
-//
-// ConfigurationsTableViewCell.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - ConfigurationsTableViewCellDelegate
-
-/// The ConfigurationsTableViewCellDelegate
-protocol ConfigurationsTableViewCellDelegate: class {
-
- /// Did select Option
- ///
- /// - Parameter option: The selected option string
- func didSelect(option: String?)
-
-}
-
-// MARK: - ConfigurationsTableViewCell
-
-/// The ConfigurationsTableViewCell
-class ConfigurationsTableViewCell: UITableViewCell, ReuseIdentifiable {
-
- // MARK: Properties
-
- /// The Configuration
- var configuration: Configuration
-
- /// The Delegate
- weak var delegate: ConfigurationsTableViewCellDelegate?
-
- /// The CollectionView
- lazy var collectionView: UICollectionView = {
- let layout = UICollectionViewFlowLayout()
- layout.scrollDirection = .horizontal
- layout.itemSize = CGSize(width: 80, height: 80)
- let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
- collectionView.contentInset = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
- collectionView.dataSource = self
- collectionView.delegate = self
- collectionView.showsVerticalScrollIndicator = false
- collectionView.showsHorizontalScrollIndicator = false
- collectionView.backgroundColor = .white
- collectionView.register(
- ConfigurationCollectionViewCell.self,
- forCellWithReuseIdentifier: ConfigurationCollectionViewCell.reuseIdentifier
- )
- return collectionView
- }()
-
- // MARK: Initializer
-
- /// Designated Initializer with Configuration
- ///
- /// - Parameter configuration: The Configuration
- init(configuration: Configuration) {
- // Set Configuration
- self.configuration = configuration
- // Super init
- super.init(
- style: .default,
- reuseIdentifier: ConfigurationCollectionViewCell.reuseIdentifier
- )
- // Add CollectionView
- self.contentView.addSubview(self.collectionView)
- // Pre-Select the first Item
- self.collectionView.selectItem(
- at: IndexPath(item: 0, section: 0),
- animated: false,
- scrollPosition: .left
- )
- }
-
- /// Initializer with Coder always return nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- // MARK: View-Lifecycle
-
- /// Layout SubViews
- override func layoutSubviews() {
- super.layoutSubviews()
- self.collectionView.frame = self.contentView.bounds
- }
-
-}
-
-// MARK: - UICollectionViewDataSource
-
-extension ConfigurationsTableViewCell: UICollectionViewDataSource {
-
- func numberOfSections(
- in collectionView: UICollectionView
- ) -> Int {
- // There is only one section
- return 1
- }
-
- func collectionView(
- _ collectionView: UICollectionView,
- numberOfItemsInSection section: Int
- ) -> Int {
- // Return the options count
- return self.configuration.options.count
- }
-
- func collectionView(
- _ collectionView: UICollectionView,
- cellForItemAt indexPath: IndexPath
- ) -> UICollectionViewCell {
- // Dequeue Cell
- let cell = collectionView.dequeueReusableCell(
- withReuseIdentifier: ConfigurationCollectionViewCell.reuseIdentifier,
- for: indexPath
- )
- // Check if Cell is a ConfigurationCollectionViewCell
- if let cell = cell as? ConfigurationCollectionViewCell {
- // Set Option Text
- cell.titleLabel.text = self.configuration.options[indexPath.item]
- }
- // Return Cell
- return cell
- }
-
-}
-
-// MARK: - UICollectionViewDelegate
-
-extension ConfigurationsTableViewCell: UICollectionViewDelegate {
-
- func collectionView(
- _ collectionView: UICollectionView,
- didSelectItemAt indexPath: IndexPath
- ) {
- // Update the selected Index
- self.configuration.selectedIndex = indexPath.item
- // Invoke delegate
- self.delegate?.didSelect(option: self.configuration.options[safe: indexPath.item])
- }
-
-}
diff --git a/Example/ExampleViewController/ExampleViewController+PresentWhatsNew.swift b/Example/ExampleViewController/ExampleViewController+PresentWhatsNew.swift
deleted file mode 100644
index 2a34345..0000000
--- a/Example/ExampleViewController/ExampleViewController+PresentWhatsNew.swift
+++ /dev/null
@@ -1,126 +0,0 @@
-//
-// ExampleViewController+PresentWhatsNew.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-import WhatsNewKit
-
-extension ExampleViewController {
-
- /// Present the WhatsNewViewController
- func presentWhatsNewViewController() {
-
- // MARK: Step 1: Initialize the WhatsNew.Items
-
- // Easy Setup Item
- let easySetupItem = WhatsNew.Item(
- title: "Easy Setup",
- subtitle: "The simple and typesafe WhatsNew struct enables you to structurize your awesome new app features",
- image: #imageLiteral(resourceName: "setup")
- )
- // Themes Item
- let themesItem = WhatsNew.Item(
- title: "Themes",
- subtitle: "You can apply different themes to perfectly match with your existing app design",
- image: #imageLiteral(resourceName: "themes")
- )
- // Installation Item
- let installationItem = WhatsNew.Item(
- title: "Installation",
- subtitle: "You can install WhatsNewKit via CocoaPods and Carthage",
- image: #imageLiteral(resourceName: "installation")
- )
- // Open Source Item
- let openSourceItem = WhatsNew.Item(
- title: "Open Source",
- subtitle: "Contributions are\nvery welcome 👨💻",
- image: #imageLiteral(resourceName: "openSource")
- )
-
- // Initialize Items
- var items = [
- easySetupItem,
- themesItem,
- installationItem,
- openSourceItem
- ]
-
- // Check if number of visible items exceeds the maximum
- if ExampleViewController.numberOfVisibleItems > ExampleViewController.maximumNumberOfVisibleItems {
- for _ in 0...2 {
- items.append(contentsOf: items)
- }
- } else {
- items = .init(
- items.dropLast(ExampleViewController.maximumNumberOfVisibleItems - ExampleViewController.numberOfVisibleItems)
- )
- }
-
- // MARK: Step 2: Initialize WhatsNew with title and items
-
- // Initialize WhatsNew
- let whatsNew = WhatsNew(
- title: "WhatsNewKit",
- items: items
- )
-
- // MARK: Step 3: Initialize a WhatsNewViewController Configuration
-
- // Initialize WhatsNewViewController Configuration in order to customize the Layout and behaviour
- var configuration = WhatsNewViewController.Configuration(
- theme: .default,
- detailButton: .init(
- // Detail Button Title
- title: "Read more",
- // Detail Button Action
- action: .website(url: "https://github.com/SvenTiigi/WhatsNewKit")
- ),
- completionButton: .init(
- // Completion Button Title
- title: "Continue",
- // Completion Button Action
- action: .dismiss
- )
- )
-
- // Example-Application specific in order to apply configurations that has been selected
- self.configurations.forEach {
- $0.configure(configuration: &configuration)
- }
-
- // MARK: Step 3: Initialize and present a WhatsNewViewController
-
- // Declare WhatsNewViewController
- let whatsNewViewController: WhatsNewViewController?
-
- // Check if a WhatsNewVersionStore is available/enabled
- if let versionStore = ExampleViewController.versionStore {
- // Initialize WhatsNewViewController with WhatsNewVersionStore
- whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew,
- configuration: configuration,
- versionStore: versionStore
- )
- } else {
- // Initialize WhatsNewViewController
- whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew,
- configuration: configuration
- )
- }
-
- /// Check if WhatsNewViewController is available
- if let controller = whatsNewViewController {
- // Present the WhatsNewViewController
- self.present(controller, animated: true)
- } else {
- // Present already presented Alert
- self.presentAlreadyPresentedAlert()
- }
- }
-
-}
diff --git a/Example/ExampleViewController/ExampleViewController.swift b/Example/ExampleViewController/ExampleViewController.swift
deleted file mode 100644
index 1cc3857..0000000
--- a/Example/ExampleViewController/ExampleViewController.swift
+++ /dev/null
@@ -1,312 +0,0 @@
-//
-// ExampleViewController.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-import SafariServices
-import WhatsNewKit
-
-// MARK: - ExampleViewController
-
-/// The ExampleViewController
-class ExampleViewController: UIViewController {
-
- // MARK: Properties
-
- /// The static WhatsNewVersionStore
- static var versionStore: WhatsNewVersionStore?
-
- /// The number of visible Items
- static var numberOfVisibleItems: Int = 4
-
- /// The maximum number of visible Items
- static let maximumNumberOfVisibleItems: Int = 4
-
- /// The HeaderView
- lazy var headerView: UIView = {
- let view = UIView()
- view.backgroundColor = .clear
- view.addSubview(self.demoImageView)
- view.addSubview(self.textLabel)
- view.addSubview(self.dividerLine)
- view.frame.size.height = 420
- return view
- }()
-
- /// The DemoImageView
- lazy var demoImageView: UIImageView = {
- let imageView = UIImageView(image: .whitePreview)
- imageView.contentMode = .scaleAspectFit
- return imageView
- }()
-
- /// The TextLabel
- lazy var textLabel: UILabel = {
- let label = UILabel()
- label.text = "WhatsNewKit enables you to easily showcase your awesome new app features"
- label.font = .systemFont(ofSize: 16, weight: .medium)
- label.textColor = .whatsNewKitDark
- label.textAlignment = .center
- label.numberOfLines = 0
- label.lineBreakMode = .byWordWrapping
- return label
- }()
-
- /// The DividerLine
- lazy var dividerLine: UIView = {
- let view = UIView()
- view.backgroundColor = .mainGray
- return view
- }()
-
- /// The TableView
- lazy var tableView: UITableView = {
- let tableView = UITableView(frame: .zero, style: .grouped)
- tableView.tableHeaderView = self.headerView
- tableView.delegate = self
- tableView.dataSource = self
- tableView.separatorStyle = .none
- tableView.backgroundColor = .white
- tableView.contentInset = UIEdgeInsets(
- top: 0,
- left: 0,
- bottom: 50,
- right: 0
- )
- return tableView
- }()
-
- /// The Configurations
- lazy var configurations: [Configuration] = [
- BackgroundColorConfiguration(),
- TintColorConfiguration(),
- AnimationConfiguration(),
- VersionStoreConfiguration(),
- LayoutConfiguration(),
- ContentModeConfiguration(),
- HapticFeedbackConfiguration(),
- SecondaryTitleColorConfiguration(),
- TitleModeConfiguration(),
- ItemsCountConfiguration()
- ]
-
- /// The Cells
- lazy var cells: [UITableViewCell] = self.configurations.map {
- let cell = ConfigurationsTableViewCell(configuration: $0)
- cell.delegate = self
- return cell
- }
-
- // MARK: Initializer
-
- /// Designated Initializer
- init() {
- super.init(nibName: nil, bundle: nil)
- }
-
- /// Required Initializer with Coder always return nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- // MARK: View-Lifecycle
-
- /// ViewDidLoad
- override func viewDidLoad() {
- super.viewDidLoad()
- // Set Title
- self.title = "WhatsNewKit"
- // Configure NavigationItem
- self.configure(navigationItem: self.navigationItem)
- // Configure Toolbar
- self.configureToolbar()
- }
-
- /// LoadView
- override func loadView() {
- // Replace View with TableView
- self.view = self.tableView
- }
-
- /// View did layout Subviews
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- self.demoImageView.frame = .init(
- x: 0,
- y: 25,
- width: self.headerView.frame.width,
- height: 300
- )
- self.textLabel.frame = .init(
- x: 20,
- y: self.demoImageView.frame.maxY + 10,
- width: self.headerView.frame.width - 40,
- height: self.headerView.frame.height - self.demoImageView.frame.maxY - 10 - 1
- )
- self.dividerLine.frame = .init(
- x: 20,
- y: self.textLabel.frame.maxY,
- width: self.headerView.frame.width - 40,
- height: 1
- )
- }
-
- // MARK: Customization
-
- /// Configure NavigationItem
- ///
- /// - Parameter navigationItem: The NavigationItem
- func configure(navigationItem: UINavigationItem) {
- // Initialize GitHub BarButtonItem
- let gitHubBarButtonItem = UIBarButtonItem(
- image: #imageLiteral(resourceName: "githubIconSmall"),
- style: .plain,
- target: self,
- action: #selector(self.gitHubBarButtonItemTouched)
- )
- navigationItem.rightBarButtonItem = gitHubBarButtonItem
- }
-
- /// Confiugure Toolbar
- func configureToolbar() {
- // Show Toolbar
- self.navigationController?.setToolbarHidden(false, animated: false)
- // Initialize FlexibleSpace BarButtonItem
- let flexibleSpaceBarButtonItem = UIBarButtonItem(
- barButtonSystemItem: .flexibleSpace,
- target: self,
- action: nil
- )
- // Initialize Present BarButtonItem
- let presentBarButtonItem = UIBarButtonItem(
- title: "Present 🤩",
- style: .done ,
- target: self,
- action: #selector(self.presentBarButtonItemTouched)
- )
- presentBarButtonItem.tintColor = .main
- // Set Toolbar Item
- self.setToolbarItems(
- [flexibleSpaceBarButtonItem, presentBarButtonItem, flexibleSpaceBarButtonItem],
- animated: false
- )
- }
-
- // MARK: BarButton Selectors
-
- /// Present GitHub ReadMe SafariViewController
- @objc
- private func gitHubBarButtonItemTouched(sender: UIBarButtonItem) {
- // Verify URL can be constructed
- guard let url = URL(string: "https://github.com/SvenTiigi/WhatsNewKit/blob/master/README.md") else {
- print("Unable to construct GitHub Repo URL")
- return
- }
- // Initialize SafariViewController with URL
- let safariViewController = SFSafariViewController(url: url)
- // Set tint color
- safariViewController.preferredControlTintColor = .main
- // Present SafariViewController
- self.present(safariViewController, animated: true)
- }
-
- @objc
- func presentBarButtonItemTouched(sender: UIBarButtonItem) {
- self.presentWhatsNewViewController()
- }
-
-}
-
-// MARK: - UITableViewDataSource
-
-extension ExampleViewController: UITableViewDataSource {
-
- func numberOfSections(
- in tableView: UITableView
- ) -> Int {
- return self.configurations.count
- }
-
- func tableView(
- _ tableView: UITableView,
- numberOfRowsInSection section: Int
- ) -> Int {
- return 1
- }
-
- func tableView(
- _ tableView: UITableView,
- cellForRowAt indexPath: IndexPath
- ) -> UITableViewCell {
- self.cells[safe: indexPath.section] ?? .init(style: .default, reuseIdentifier: "invalidCell")
- }
-
- func tableView(
- _ tableView: UITableView,
- titleForHeaderInSection section: Int
- ) -> String? {
- return self.configurations[safe: section]?.title
- }
-
- func tableView(
- _ tableView: UITableView,
- titleForFooterInSection section: Int
- ) -> String? {
- return self.configurations[safe: section]?.subtitle
- }
-
-}
-
-// MARK: - UITableViewDelegate
-
-extension ExampleViewController: UITableViewDelegate {
-
- func tableView(
- _ tableView: UITableView,
- heightForRowAt indexPath: IndexPath
- ) -> CGFloat {
- return 110
- }
-
-}
-
-
-// MARK: - ConfigurationsTableViewCellDelegate
-
-extension ExampleViewController: ConfigurationsTableViewCellDelegate {
-
- /// Did select Option
- ///
- /// - Parameter option: The selected option string
- func didSelect(option: String?) {
- if option == "White" {
- self.demoImageView.image = .whitePreview
- }
- if option == "Dark" {
- self.demoImageView.image = .darkPreview
- }
- }
-
-}
-
-// MARK: - Already Presented
-
-extension ExampleViewController {
-
- /// Present already Presented Alert
- func presentAlreadyPresentedAlert() {
- let alertController = UIAlertController(
- title: "WhatsNewVersionStore",
- message: "The Version \(WhatsNew.Version.current().description) has already been presented",
- preferredStyle: .alert
- )
- let confirmAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
- alertController.addAction(confirmAction)
- self.present(alertController, animated: true)
- }
-
-}
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 212b38a..0000000
--- a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,113 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "iphone",
- "size" : "20x20",
- "scale" : "2x"
- },
- {
- "idiom" : "iphone",
- "size" : "20x20",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-Small@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-Small@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-Small-40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-Small-40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-60@3x.png",
- "scale" : "3x"
- },
- {
- "idiom" : "ipad",
- "size" : "20x20",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "20x20",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-Small.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-Small@2x.png",
- "scale" : "2x"
- },
- {
- "idiom" : "ipad",
- "size" : "40x40",
- "scale" : "1x"
- },
- {
- "idiom" : "ipad",
- "size" : "40x40",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-76.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-167.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "iTunesArtwork@2x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- },
- "properties" : {
- "pre-rendered" : true
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-167.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-167.png
deleted file mode 100644
index 2c0e092..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-167.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
deleted file mode 100644
index b309c86..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
deleted file mode 100644
index c8e51b3..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76.png
deleted file mode 100644
index 5a61bf7..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
deleted file mode 100644
index d04a2c1..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
deleted file mode 100644
index a6db5dc..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
deleted file mode 100644
index 9b9de3f..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
deleted file mode 100644
index 4bd1a32..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
deleted file mode 100644
index 5be3d43..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
deleted file mode 100644
index 4fdee0a..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png b/Example/Resources/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png
deleted file mode 100644
index c5bb453..0000000
Binary files a/Example/Resources/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/Contents.json b/Example/Resources/Assets.xcassets/Contents.json
deleted file mode 100644
index da4a164..0000000
--- a/Example/Resources/Assets.xcassets/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/Preview/Contents.json b/Example/Resources/Assets.xcassets/Preview/Contents.json
deleted file mode 100644
index da4a164..0000000
--- a/Example/Resources/Assets.xcassets/Preview/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/Preview/darkPreview.imageset/Contents.json b/Example/Resources/Assets.xcassets/Preview/darkPreview.imageset/Contents.json
deleted file mode 100644
index b4004a7..0000000
--- a/Example/Resources/Assets.xcassets/Preview/darkPreview.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "darkPreview.jpg",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/Preview/darkPreview.imageset/darkPreview.jpg b/Example/Resources/Assets.xcassets/Preview/darkPreview.imageset/darkPreview.jpg
deleted file mode 100644
index c8f4c51..0000000
Binary files a/Example/Resources/Assets.xcassets/Preview/darkPreview.imageset/darkPreview.jpg and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/Preview/whitePreview.imageset/Contents.json b/Example/Resources/Assets.xcassets/Preview/whitePreview.imageset/Contents.json
deleted file mode 100644
index 2e7200b..0000000
--- a/Example/Resources/Assets.xcassets/Preview/whitePreview.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "whitepreview.jpg",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/Preview/whitePreview.imageset/whitepreview.jpg b/Example/Resources/Assets.xcassets/Preview/whitePreview.imageset/whitepreview.jpg
deleted file mode 100644
index ad31692..0000000
Binary files a/Example/Resources/Assets.xcassets/Preview/whitePreview.imageset/whitepreview.jpg and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/Contents.json b/Example/Resources/Assets.xcassets/WhatsNewItemImages/Contents.json
deleted file mode 100644
index da4a164..0000000
--- a/Example/Resources/Assets.xcassets/WhatsNewItemImages/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/installation.imageset/Contents.json b/Example/Resources/Assets.xcassets/WhatsNewItemImages/installation.imageset/Contents.json
deleted file mode 100644
index ab7b389..0000000
--- a/Example/Resources/Assets.xcassets/WhatsNewItemImages/installation.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "icons8-puzzle-100.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/installation.imageset/icons8-puzzle-100.png b/Example/Resources/Assets.xcassets/WhatsNewItemImages/installation.imageset/icons8-puzzle-100.png
deleted file mode 100644
index 81aa7c1..0000000
Binary files a/Example/Resources/Assets.xcassets/WhatsNewItemImages/installation.imageset/icons8-puzzle-100.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/openSource.imageset/Contents.json b/Example/Resources/Assets.xcassets/WhatsNewItemImages/openSource.imageset/Contents.json
deleted file mode 100644
index 99c545b..0000000
--- a/Example/Resources/Assets.xcassets/WhatsNewItemImages/openSource.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "icons8-github-100.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/openSource.imageset/icons8-github-100.png b/Example/Resources/Assets.xcassets/WhatsNewItemImages/openSource.imageset/icons8-github-100.png
deleted file mode 100644
index f149402..0000000
Binary files a/Example/Resources/Assets.xcassets/WhatsNewItemImages/openSource.imageset/icons8-github-100.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/setup.imageset/Contents.json b/Example/Resources/Assets.xcassets/WhatsNewItemImages/setup.imageset/Contents.json
deleted file mode 100644
index 316996f..0000000
--- a/Example/Resources/Assets.xcassets/WhatsNewItemImages/setup.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "icons8-approval-100.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/setup.imageset/icons8-approval-100.png b/Example/Resources/Assets.xcassets/WhatsNewItemImages/setup.imageset/icons8-approval-100.png
deleted file mode 100644
index f1a6c6d..0000000
Binary files a/Example/Resources/Assets.xcassets/WhatsNewItemImages/setup.imageset/icons8-approval-100.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/themes.imageset/Contents.json b/Example/Resources/Assets.xcassets/WhatsNewItemImages/themes.imageset/Contents.json
deleted file mode 100644
index 178484b..0000000
--- a/Example/Resources/Assets.xcassets/WhatsNewItemImages/themes.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "icons8-picture-100.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/WhatsNewItemImages/themes.imageset/icons8-picture-100.png b/Example/Resources/Assets.xcassets/WhatsNewItemImages/themes.imageset/icons8-picture-100.png
deleted file mode 100644
index 03b6e31..0000000
Binary files a/Example/Resources/Assets.xcassets/WhatsNewItemImages/themes.imageset/icons8-picture-100.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/Contents.json b/Example/Resources/Assets.xcassets/githubIconSmall.imageset/Contents.json
deleted file mode 100644
index 1d3ecbb..0000000
--- a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "filename" : "icons8-github-filled-26.png",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "icons8-github-filled-52.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "filename" : "icons8-github-filled-78.png",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-26.png b/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-26.png
deleted file mode 100644
index 57c743d..0000000
Binary files a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-26.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-52.png b/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-52.png
deleted file mode 100644
index 38816c5..0000000
Binary files a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-52.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-78.png b/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-78.png
deleted file mode 100644
index 1b6741b..0000000
Binary files a/Example/Resources/Assets.xcassets/githubIconSmall.imageset/icons8-github-filled-78.png and /dev/null differ
diff --git a/Example/Resources/Assets.xcassets/roundedAppIcon.imageset/Contents.json b/Example/Resources/Assets.xcassets/roundedAppIcon.imageset/Contents.json
deleted file mode 100644
index 0723449..0000000
--- a/Example/Resources/Assets.xcassets/roundedAppIcon.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "iTunesArtwork@2x.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Example/Resources/Assets.xcassets/roundedAppIcon.imageset/iTunesArtwork@2x.png b/Example/Resources/Assets.xcassets/roundedAppIcon.imageset/iTunesArtwork@2x.png
deleted file mode 100644
index f84dcfd..0000000
Binary files a/Example/Resources/Assets.xcassets/roundedAppIcon.imageset/iTunesArtwork@2x.png and /dev/null differ
diff --git a/Example/Resources/Base.lproj/LaunchScreen.storyboard b/Example/Resources/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index a2c4d71..0000000
--- a/Example/Resources/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Example/Resources/Info.plist b/Example/Resources/Info.plist
deleted file mode 100644
index 210b51b..0000000
--- a/Example/Resources/Info.plist
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- WhatsNewKit
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 1.3.7
- CFBundleVersion
- 1
- LSRequiresIPhoneOS
-
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
- UIInterfaceOrientationPortraitUpsideDown
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIUserInterfaceStyle
- Light
-
-
diff --git a/Example/Shared/Collection+Safe.swift b/Example/Shared/Collection+Safe.swift
deleted file mode 100644
index 3d7e80a..0000000
--- a/Example/Shared/Collection+Safe.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// Collection+Safe.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import Foundation
-
-extension Collection {
-
- /// Returns the element at the specified index if it is within bounds, otherwise nil.
- subscript (safe index: Index) -> Element? {
- return indices.contains(index) ? self[index] : nil
- }
-
-}
diff --git a/Example/Shared/ReuseIdentifiable.swift b/Example/Shared/ReuseIdentifiable.swift
deleted file mode 100644
index 0d120b0..0000000
--- a/Example/Shared/ReuseIdentifiable.swift
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// ReuseIdentifiable.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import Foundation
-
-// MARK: - ReuseIdentifiable
-
-/// The ReuseIdentifiable
-protocol ReuseIdentifiable {
-
- /// The reuse Identifier
- static var reuseIdentifier: String { get }
-
-}
-
-// MARK: - ReuseIdentifiable Default Implementation
-
-extension ReuseIdentifiable {
-
- /// The reuse Identifier
- static var reuseIdentifier: String {
- // Return SubjectType of self as String
- return String(describing: Mirror(reflecting: self).subjectType)
- }
-
-}
diff --git a/Example/Shared/UIColor+Main.swift b/Example/Shared/UIColor+Main.swift
deleted file mode 100644
index aa764b1..0000000
--- a/Example/Shared/UIColor+Main.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// UIColor+Main.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 28.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-import WhatsNewKit
-
-extension UIColor {
-
- // The main Color
- static let main: UIColor = .whatsNewKitBlue
-
- /// The main gray color
- static let mainGray: UIColor = .init(red: 199 / 255, green: 199 / 255, blue: 204 / 255, alpha: 1)
-
-}
diff --git a/Example/Shared/UIImage+Assets.swift b/Example/Shared/UIImage+Assets.swift
deleted file mode 100644
index da230eb..0000000
--- a/Example/Shared/UIImage+Assets.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// UIImage+Assets.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 20.10.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-extension UIImage {
-
- /// The white preview image
- static let whitePreview = UIImage(named: "whitePreview")
-
- /// The dark preview image
- static let darkPreview = UIImage(named: "darkPreview")
-
-}
diff --git a/LICENSE b/LICENSE
index 3643f50..9a589c9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2020 Sven Tiigi
+Copyright (c) 2022 Sven Tiigi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Package.swift b/Package.swift
index 6203e1f..fa03e7e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,29 +1,28 @@
-// swift-tools-version:5.0
+// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "WhatsNewKit",
platforms: [
- .iOS(.v9)
+ .iOS(.v13),
+ .macOS(.v11)
],
products: [
.library(
name: "WhatsNewKit",
targets: ["WhatsNewKit"]
- ),
+ )
],
- dependencies: [],
targets: [
.target(
name: "WhatsNewKit",
- dependencies: [],
path: "Sources"
),
.testTarget(
name: "WhatsNewKitTests",
dependencies: ["WhatsNewKit"],
path: "Tests"
- ),
+ )
]
)
diff --git a/README.md b/README.md
index c273780..b32aea2 100644
--- a/README.md
+++ b/README.md
@@ -1,675 +1,530 @@
+
+
-
+
+
+
+
+ WhatsNewKit
+
+
+
+ A Swift Package to easily showcase your new app features.
+
+ It's designed from the ground up to be fully customized to your needs.
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
- WhatsNewKit enables you to easily showcase your awesome new app features. It's designed from the ground up to be fully customized to your needs.
-
-
-
+```swift
+import SwiftUI
+import WhatsNewKit
-
-
-
+struct ContentView: View {
+
+ var body: some View {
+ NavigationView {
+ // ...
+ }
+ .whatsNewSheet()
+ }
+
+}
+```
## Features
-- [x] Customization and Configuration to your needs 💪
-- [x] Predefined Themes and Animations 🎬
-- [x] Easily check if your Features have already been presented 🎁
-- [x] Awesome UI 😍
-
-## Example
-
-The example Application is an excellent way to see `WhatsNewKit` in action. You get a brief look of the available configuration options and how they affect the look and feel of the `WhatsNewViewController`. Simply open the `WhatsNewKit.xcodeproj` and run the `WhatsNewKit-Example` scheme.
-
-
-
-
+- [x] Easily present your new app features 🤩
+- [x] Automatic & Manual presentation mode ✅
+- [x] Support for SwiftUI, UIKit and AppKit 🧑🎨
+- [x] Runs on iOS and macOS 📱 🖥
+- [x] Adjustable layout 🔧
## Installation
-### CocoaPods
-
-WhatsNewKit is available through [CocoaPods](http://cocoapods.org). To install
-it, simply add the following line to your Podfile:
-
-```bash
-pod 'WhatsNewKit'
-```
-
-### Carthage
-
-[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
-
-To integrate WhatsNewKit into your Xcode project using Carthage, specify it in your `Cartfile`:
-
-```ogdl
-github "SvenTiigi/WhatsNewKit"
-```
-
-Run `carthage update --platform iOS` to build the framework and drag the built `WhatsNewKit.framework` into your Xcode project.
-
-On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase” and add the Framework path as mentioned in [Carthage Getting started Step 4, 5 and 6](https://github.com/Carthage/Carthage/blob/master/README.md#if-youre-building-for-ios-tvos-or-watchos)
-
### Swift Package Manager
To integrate using Apple's [Swift Package Manager](https://swift.org/package-manager/), add the following as a dependency to your `Package.swift`:
```swift
dependencies: [
- .package(url: "https://github.com/SvenTiigi/WhatsNewKit.git", from: "1.3.0")
+ .package(url: "https://github.com/SvenTiigi/WhatsNewKit.git", from: "2.0.0")
]
```
-### Manually
+Or navigate to your Xcode project then select `Swift Packages`, click the “+” icon and search for `WhatsNewKit`.
-If you prefer not to use any of the aforementioned dependency managers, you can integrate WhatsNewKit into your project manually. Simply drag the `Sources` Folder into your Xcode project.
+## Example
-## Usage
-The following first usage description shows the easiest way of presenting your new app features with `WhatsNewKit`.
+Check out the example application to see WhatsNewKit in action. Simply open the `Example/Example.xcodeproj` and run the "Example" scheme.
-> 👨💻 Please see the [Advanced](https://github.com/SvenTiigi/WhatsNewKit#advanced) section for further configuration options and features.
+
+
+
-```swift
-import WhatsNewKit
+## Usage
-// Initialize WhatsNew
-let whatsNew = WhatsNew(
- // The Title
- title: "WhatsNewKit",
- // The features you want to showcase
- items: [
- WhatsNew.Item(
- title: "Installation",
- subtitle: "You can install WhatsNewKit via CocoaPods or Carthage",
- image: UIImage(named: "installation")
- ),
- WhatsNew.Item(
- title: "Open Source",
- subtitle: "Contributions are very welcome 👨💻",
- image: UIImage(named: "openSource")
- )
- ]
-)
+### Table of contents
-// Initialize WhatsNewViewController with WhatsNew
-let whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew
-)
+- [Manual Presentation](https://github.com/SvenTiigi/WhatsNewKit/tree/master#manual-presentation)
+- [Automatic Presentation](https://github.com/SvenTiigi/WhatsNewKit/tree/master#automatic-presentation)
+- [WhatsNewEnvironment](https://github.com/SvenTiigi/WhatsNewKit/tree/master#whatsnewenvironment)
+- [WhatsNewVersionStore](https://github.com/SvenTiigi/WhatsNewKit/tree/master#whatsnewversionstore)
+- [WhatsNew](https://github.com/SvenTiigi/WhatsNewKit/tree/master#whatsnew)
+- [Layout](https://github.com/SvenTiigi/WhatsNewKit/tree/master#layout)
+- [WhatsNewViewController](https://github.com/SvenTiigi/WhatsNewKit/tree/master#whatsnewviewcontroller)
-// Present it 🤩
-self.present(whatsNewViewController, animated: true)
-```
+### Manual Presentation
-Additionally, you can make use of the `WhatsNewVersionStore` to show the `WhatsNewViewController` only once for each version of your app.
+If you wish to manually present a `WhatsNewView` you can make use of the `sheet(whatsNew:)` modifier.
```swift
-// Initialize WhatsNewVersionStore
-let versionStore: WhatsNewVersionStore = KeyValueWhatsNewVersionStore()
-
-// Passing a WhatsNewVersionStore to the initializer
-// will give you an optional WhatsNewViewController
-let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
- whatsNew: whatsNew,
- versionStore: versionStore
-)
-
-// Verify WhatsNewViewController is available
-guard let viewController = whatsNewViewController else {
- // The user has already seen the WhatsNew-Screen for the current Version of your app
- return
+struct ContentView: View {
+
+ @State
+ var whatsNew: WhatsNew? = WhatsNew(
+ title: "WhatsNewKit",
+ features: [
+ .init(
+ image: .init(
+ systemName: "star.fill",
+ foregroundColor: .orange
+ ),
+ title: "Showcase your new App Features",
+ subtitle: "Present your new app features..."
+ ),
+ // ...
+ ]
+ )
+
+ var body: some View {
+ NavigationView {
+ // ...
+ }
+ .sheet(
+ whatsNew: self.$whatsNew
+ )
+ }
+
}
-
-// Present WhatsNewViewController
-self.present(viewController, animated: true)
```
-> 👨💻 Head over to the [WhatsNewVersionStore](https://github.com/SvenTiigi/WhatsNewKit#whatsnewversionstore-) to learn more.
-## Advanced
-As mentioned before `WhatsNewKit` can be fully customized to your needs. The Advanced section will explain all configuration possibilities and features of `WhatsNewKit` in detail. First off it's important to understand the components of the `WhatsNewViewController` in order to customize the behaviour and `UI`-Design.
+### Automatic Presentation
-
-
-
+The automatic presentation mode allows you to simply declare your new features via the SwiftUI Environment and WhatsNewKit will take care to present the corresponding `WhatsNewView`.
-### WhatsNewViewController.Configuration
-The [WhatsNewViewController.Configuration](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BConfiguration.swift) struct enables you to customize the `WhatsNewViewController` components to your needs. The configuration itself can be passed to the initializer of the `WhatsNewViewController`.
+First add a `.whatsNewSheet()` modifier to the view where the `WhatsNewView` should be presented on.
```swift
-// Initialize default Configuration
-var configuration = WhatsNewViewController.Configuration()
-
-// Customize Configuration to your needs
-configuration.backgroundColor = .white
-configuration.titleView.titleColor = .orange
-configuration.itemsView.titleFont = .systemFont(ofSize: 17)
-configuration.detailButton?.titleColor = .orange
-configuration.completionButton.backgroundColor = .orange
-// And many more configuration properties...
-
-// Initialize WhatsNewViewController with custom configuration
-let whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew,
- configuration: configuration
-)
-```
-
-### Theme 🌄
-A [Theme](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BConfiguration.swift) allows you to group the customization of a `WhatsNewViewController.Configuration`. `WhatsNewKit` implemented predefined Themes which are available as static properties both in white and dark mode. Or you create your very own Theme to configure it to your needs.
-
-| `.darkRed` | `.whiteRed` |
-| ------------- | ------------- |
-|
|
|
+struct ContentView: View {
-```swift
-// Configuration with predefined Dark Red Theme
-let darkRed = WhatsNewViewController.Configuration(
- theme: .darkRed
-)
+ var body: some View {
+ NavigationView {
+ // ...
+ }
+ // Automatically present a WhatsNewView, if needed.
+ // The WhatsNew that should be presented to the user
+ // is automatically retrieved from the `WhatsNewEnvironment`
+ .whatsNewSheet()
+ }
-// Apply predefined White Red Theme to Configuration
-var configuration = WhatsNewViewController.Configuration()
-configuration.apply(theme: .whiteRed)
-
-// Or create your own Theme and initialize a Configuration with your Theme
-let myTheme = WhatsNewViewController.Theme { configuration in
- configuration.backgroundColor = .white
- configuration.titleView.titleColor = .orange
- configuration.itemsView.titleFont = .systemFont(ofSize: 17)
- configuration.detailButton?.titleColor = .orange
- configuration.completionButton.backgroundColor = .orange
- // ...
}
-
-// Initialize a Configuration with your Theme
-let configuration = WhatsNewViewController.Configuration(
- theme: myTheme
-)
```
-For a full overview of the available predefined Themes check out the [Example-Application](https://github.com/SvenTiigi/WhatsNewKit/tree/master/Example).
-
-#### iOS 13 Dark-Mode
-
-Use the `.red` Theme if you wish that a predefined Theme like `.darkRed` and `.whiteRed` automatically adapts to the current [UserInterfaceStyle](https://developer.apple.com/documentation/uikit/uiuserinterfacestyle).
+The `.whatsNewSheet()` modifier is making use of the `WhatsNewEnvironment` to retrieve an optional WhatsNew object that should be presented to the user for the current version. Therefore you can easily configure the `WhatsNewEnvironment` via the `environment` modifier.
```swift
-// Configuration with predefine `red` Theme which auto adapts to the UserInterfaceStyle
-// in order to support iOS 13 Dark-Mode
-let configuration = WhatsNewViewController.Configuration(
- theme: .red
-)
-```
-
-Dark-Mode compatible Themes: `.blue`, `.lightBlue`, `.orange`, `.purple`, `.red`, `.green`
-
-### Layout 📐
+extension App: SwiftUI.App {
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ .environment(
+ \.whatsNew,
+ WhatsNewEnvironment(
+ // Specify in which way the presented WhatsNew Versions are stored.
+ // In default the `UserDefaultsWhatsNewVersionStore` is used.
+ versionStore: UserDefaultsWhatsNewVersionStore(),
+ // Pass a `WhatsNewCollectionProvider` or an array of WhatsNew instances
+ whatsNewCollection: self
+ )
+ )
+ }
+ }
-`WhatsNewKit` comes with three predefined [`ItemsView.Layouts`](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BItemsView.swift).
+}
-| Left | Centered | Right |
-| ------------- | ------------- | ------------- |
-| | | |
+// MARK: - App+WhatsNewCollectionProvider
-```swift
-// Left Layout
-configuration.itemsView.layout = .left
+extension App: WhatsNewCollectionProvider {
-// Centered Layout
-configuration.itemsView.layout = .centered
+ /// Declare your WhatsNew instances per version
+ var whatsNewCollection: WhatsNewCollection {
+ WhatsNew(
+ version: "1.0.0",
+ // ...
+ )
+ WhatsNew(
+ version: "1.1.0",
+ // ...
+ )
+ WhatsNew(
+ version: "1.2.0",
+ // ...
+ )
+ }
-// Right Layout
-configuration.itemsView.layout = .right
+}
```
-> ☝️ In default the ItemsView layout is set to `.left`.
-
-### ContentMode 📏
-
-Setting the [`ContentMode`](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BItemsView.swift) in the `ItemsView` Configuration will adjust for how your features are arranged along the axis.
-
-| Top | Center | Fill |
-| ------------- | ------------- | ------------- |
-| | |
-
-```swift
-// ContentMode Top
-configuration.itemsView.contentMode = .top
-// ContentMode Center
-configuration.itemsView.contentMode = .center
+## WhatsNewEnvironment
-// ContentMode Fill
-configuration.itemsView.contentMode = .fill
-```
-> ☝️ In default the ItemsView ContentMode is set to `top`.
-
-### Insets
+The `WhatsNewEnvironment` will take care to determine the matching WhatsNew object that should be presented to the user for the current version.
-Additionally, if you wish you can modify the layout insets of the `WhatsNewViewController` components.
+As seen in the previous example you can initialize a `WhatsNewEnvironment` by specifying the `WhatsNewVersionStore` and providing a `WhatsNewCollection`.
```swift
-// Set TitleView Insets (Default values)
-configuration.titleView.insets = UIEdgeInsets(top: 50, left: 20, bottom: 15, right: 20)
+// Initialize WhatsNewEnvironment by passing an array of WhatsNew Instances.
+// UserDefaultsWhatsNewVersionStore is used as default WhatsNewVersionStore
+let whatsNewEnvironment = WhatsNewEnvironment(
+ whatsNewCollection: [
+ WhatsNew(
+ version: "1.0.0",
+ // ...
+ )
+ ]
+)
-// Increase the CompletionButton Bottom Inset
-configuration.completionButton.insets.bottom += 10
+// Initialize WhatsNewEnvironment with NSUbiquitousKeyValueWhatsNewVersionStore
+// which stores the presented versions in iCloud.
+// WhatsNewCollection is provided by a `WhatsNewBuilder` closure
+let whatsNewEnvironment = WhatsNewEnvironment(
+ versionStore: NSUbiquitousKeyValueWhatsNewVersionStore(),
+ whatsNewCollection: {
+ WhatsNew(
+ version: "1.0.0",
+ // ...
+ )
+ }
+)
```
-### ImageSize
-In order to define the size of your images for each of your feature you can set an [ImageSize](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BItemsView.swift) on the `ItemsView` configuration.
-
-```swift
-// Use the original image size as it is
-configuration.itemsView.imageSize = .original
-
-// Use the preferred image size which fits perfectly :)
-configuration.itemsView.imageSize = .preferred
-
-// Use a custom height for each image
-configuration.itemsView.imageSize = .fixed(height: 25)
-```
-> ☝️ In default the ItemsView ImageSize is set to `preferred`.
+Additionally, the `WhatsNewEnvironment` includes a fallback for patch versions. For example when a user installs version `1.0.1` and you only have declared a `WhatsNew` for version `1.0.0` the environment will automatically fallback to version `1.0.0` and present the `WhatsNewView` to the user if needed.
-### Image Tint-Color
-In default WhatsNewKit auto tints the images of each `WhatsNew.Item` in the given tint color of the configuration.
-If you wish to disable this functionality simply set `autoTintImage` to `false`
+If you wish to further customize the behaviour of the `WhatsNewEnvironment` you can easily subclass it and override the `whatsNew()` function.
```swift
-// Disable auto tinting images
-configuration.itemsView.autoTintImage = false
+class MyCustomWhatsNewEnvironment: WhatsNewEnvironment {
+
+ /// Retrieve a WhatsNew that should be presented to the user, if available.
+ override func whatsNew() -> WhatsNew? {
+ // The current version
+ let currentVersion = self.currentVersion
+ // Your declared WhatsNew objects
+ let whatsNewCollection = self.whatsNewCollection
+ // The WhatsNewVersionStore used to determine the already presented versions
+ let versionStore = self.whatsNewVersionStore
+ // TODO: Determine WhatsNew that should be presented to the user...
+ }
+
+}
```
-> ☝️ In default the ItemsView AutoTintImage is set to `true`.
-### Animation 🎬
-
+## WhatsNewVersionStore
-You can apply a [Animation](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BAnimation.swift) to all components of the `WhatsNewViewController` via predefined animation types. In default all Animation properties are `nil` indicating no animation should be perfomed.
+A `WhatsNewVersionStore` is a protocol type which is responsible for saving and retrieving versions that have been presented to the user.
```swift
-// Set SlideUp Animation to TitleView
-configuration.titleView.animation = .slideUp
-
-// Set SlideRight Animation to ItemsView
-configuration.itemsView.animation = .slideRight
-
-// Set SlideLeft Animation to DetailButton
-configuration.detailButton?.animation = .slideLeft
+let whatsNewVersionStore: WhatsNewVersionStore
-// Set SlideDown Animation to CompletionButton
-configuration.completionButton.animation = .slideDown
-```
+// Save presented versions
+whatsNewVersionStore.save(presentedVersion: "1.0.0")
-If you wish to animate all views with the same type you can do so by simply applying it to the configuration.
+// Retrieve presented versions
+let presentedVersions = whatsNewVersionStore.presentedVersions
-```swift
-// Global Animation-Type for all WhatsNewViewController components
-configuration.apply(animation: .fade)
+// Retrieve bool value if a given version has already been presented
+let hasPresented = whatsNewVersionStore.hasPresented("1.0.0")
```
-If you wish to define your custom animation, simply set the `custom` enum and pass an animator closure.
+WhatsNewKit comes along with three predefined implementations:
```swift
-// Custom Animation for DetailButton
-configuration.detailButton?.animation = .custom(animator: { (view: UIView, settings: AnimatorSettings) in
- // view: The View to perform animation on
- // settings: Preferred duration and delay
-})
-```
-
-### Title Mode
-In default the TitleView is sticked to the top.
-If you wish that the TitleView scrolls with the ItemsView you can change the `titleMode` on the TitleView configuration.
-
-```swift
-// TitleView scrolls alongside with the ItemsView
-configuration.titleView.titleMode = .scrolls
-
-// TitleView is fixed to top
-configuration.titleView.titleMode = .fixed
-```
-> ☝️ In default the titleMode is set to `.fixed`.
+// Persists presented versions in the UserDefaults
+let userDefaultsWhatsNewVersionStore = UserDefaultsWhatsNewVersionStore()
-### Secondary Title Color
-By setting a [SecondaryColor](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BTitleView.swift) on the TitleView you can change the color of certain characters.
+// Persists presented versions in iCloud using the NSUbiquitousKeyValueStore
+let ubiquitousKeyValueWhatsNewVersionStore = NSUbiquitousKeyValueWhatsNewVersionStore()
-
-
-```swift
-// Set secondary color on TitleView Configuration
-configuration.titleView.secondaryColor = .init(
- // The start index
- startIndex: 0,
- // The length of characters
- length: 5,
- // The secondary color to apply
- color: .whatsNewKitLightBlue
-)
+// Stores presented versions in memory. Perfect for testing purposes
+let inMemoryWhatsNewVersionStore = InMemoryWhatsNewVersionStore()
```
-> ☝️ In default the secondaryColor is set to `nil`.
-### DetailButton
-
+If you already have a specific implementation to store user related settings like Realm or Core Data you can easily adopt your existing implementation to the `WhatsNewVersionStore` protocol.
-By setting an [DetailButton](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BDetailButton.swift) struct on the `WhatsNewViewController.Configuration` struct you can customize the `title` and the corresponding `action` of the displayed detail button on the `WhatsNewViewController`. As the `DetailButton` struct is declared as optional the `WhatsNewViewController` will only display the button if a `DetailButton` configuration is available
-
-| Action | Description |
-| ------------- | ------------- |
-| `website` | When the user pressed the detail button a `SFSafariViewController` with the given `URL` will be presented |
-| `custom` | After the detail button has been pressed by the user, your custom action will be invoked |
-
-```swift
-// Initialize DetailButton with title and open website at url
-let detailButton = WhatsNewViewController.DetailButton(
- title: "Read more",
- action: .website(url: "https://github.com/SvenTiigi/WhatsNewKit")
-)
+### NSUbiquitousKeyValueWhatsNewVersionStore
-// Initialize DetailButton with title and custom action
-let detailButton = WhatsNewViewController.DetailButton(
- title: "Read more",
- action: .custom(action: { [weak self] whatsNewViewController in
- // Perform custom action on detail button pressed
- })
-)
-```
+If you are making use of the `NSUbiquitousKeyValueWhatsNewVersionStore` please ensure to enable the [iCloud Key-value storage](https://developer.apple.com/library/archive/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForKey-ValueDataIniCloud.html) capability in the "Signing & Capabilities" section of your Xcode project.
-### CompletionButton
-
+
+
+
-The [CompletionButton](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BCompletionButton.swift) struct configures the displayed title and the action when the user pressed the completion button on the `WhatsNewViewController`.
+## WhatsNew
-| Action | Description |
-| ------------- | ------------- |
-| `dismiss` | When the user pressed the completion button, the `WhatsNewViewController` will be dismissed. This is the default value |
-| `custom` | After the completion button has been pressed by the user, your custom action will be invoked |
+The following sections explains how a `WhatsNew` struct can be initialized in order to describe the new features for a given version of your app.
```swift
-// Initialize CompletionButton with title and dismiss action
-let completionButton = WhatsNewViewController.CompletionButton(
- title: "Continue",
- action: .dismiss
-)
-
-// Initialize CompletionButton with title and custom action
-let completionButton = WhatsNewViewController.CompletionButton(
- title: "Continue",
- action: .custom(action: { [weak self] whatsNewViewController in
- // Perform custom action on completion button pressed
- })
+let whatsnew = WhatsNew(
+ // The Version that relates to the features you want to showcase
+ version: "1.0.0",
+ // The title that is shown at the top
+ title: "What's New",
+ // The features you want to showcase
+ features: [
+ WhatsNew.Feature(
+ image: .init(systemName: "star.fill"),
+ title: "Title",
+ subtitle: "Subtitle"
+ )
+ ],
+ // The primary action that is used to dismiss the WhatsNewView
+ primaryAction: WhatsNew.PrimaryAction(
+ title: "Continue",
+ backgroundColor: .accentColor,
+ foregroundColor: .white,
+ hapticFeedback: .notification(.success),
+ onDimiss: {
+ print("WhatsNewView has been dismissed")
+ }
+ ),
+ // The optional secondary action that is displayed above the primary action
+ secondaryAction: WhatsNew.SecondaryAction(
+ title: "Learn more",
+ foregroundColor: .accentColor,
+ hapticFeedback: .selection,
+ action: .openURL(
+ .init(string: "https://github.com/SvenTiigi/WhatsNewKit")
+ )
+ )
)
```
-### HapticFeedback 📳
+### WhatsNew.Version
-You can enable on both `DetailButton` and `CompletionButton` haptic feedback when the user pressed one of these buttons. Either by setting the property or passing it to the initializer.
+The `WhatsNew.Version` specifies the version that has introduced certain features to your app.
```swift
-// Impact Feedback
-button.hapticFeedback = .impact(.medium)
-
-// Selection Feedback
-button.hapticFeedback = .selection
-
-// Notification Feedback with type
-let completionButton = WhatsNewViewController.CompletionButton(
- title: "Continue",
- action: .dismiss,
- hapticFeedback: .notification(.success)
+// Initialize with major, minor, and patch
+let version = WhatsNew.Version(
+ major: 1,
+ minor: 0,
+ patch: 0
)
-```
-> ☝️ In default the [HapticFeedback](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Configuration/WhatsNewViewController%2BHapticFeedback.swift) is `nil` indicating no haptic feedback should be executed.
-
-### iPad Adjustments
-If you wish to modify the layout of the `WhatsNewViewController` when presenting it on an iPad you can set the `padAdjustment` closure.
-
-Currently the `padAdjustment` closure will only look for changed `insets` property of the `WhatsNewViewController.Configuration` in order to update the layout. Therefore, changes to any other configuration property will have no effect.
+// Initialize by string literal
+let version: WhatsNew.Version = "1.0.0"
-```swift
-// Set PadAdjustment closure
-configuration.padAdjustment = { configuration in
- // Invoke default PadAdjustments (Adjusts Insets for iPad)
- WhatsNewViewController.Configuration.defaultPadAdjustment(&configuration)
- // Adjust TitleView top insets
- configuration.titleView.insets.top = 20
-}
+// Initialize WhatsNew Version by using the current version of your bundle
+let version: WhatsNew.Version = .current()
```
-> ☝️ In default the `WhatsNewViewController.Configuration.defaultPadAdjustment` will be invoked.
-### WhatsNewVersionStore 💾
-
-
-
+### WhatsNew.Title
-If we speak about presenting awesome new app features we have to take care that this kind of `UI` action only happens once if the user installed the app or opened it after an update. The `WhatsNewKit` offers a protocol oriented solution for this kind of problem via the [WhatsNewVersionStore](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Store/WhatsNewVersionStore.swift) protocol.
+A `WhatsNew.Title` represents the title text that is rendered above the features.
```swift
-/// WhatsNewVersionStore typealias protocol composition
-public typealias WhatsNewVersionStore = WriteableWhatsNewVersionStore & ReadableWhatsNewVersionStore
+// Initialize by string literla
+let title: WhatsNew.Title = "Continue"
-/// The WriteableWhatsNewVersionStore
-public protocol WriteableWhatsNewVersionStore {
- func set(version: WhatsNew.Version)
-}
+// Initialize with text and foreground color
+let title = WhatsNew.Title(
+ text: "Continue",
+ foregroundColor: .primary
+)
-/// The ReadableWhatsNewVersionStore
-public protocol ReadableWhatsNewVersionStore {
- func has(version: WhatsNew.Version) -> Bool
-}
+// On >= iOS 15 initialize with AttributedString using Markdown
+let title = WhatsNew.Title(
+ text: try AttributedString(
+ markdown: "What's **New**"
+ )
+)
```
-The `WhatsNewViewController` will use the APIs of the `WhatsNewVersionStore` in the following way.
-
-| API | Description |
-| ------------- | ------------- |
-| `has(version:)` | Checks if the `Whatsnew.Version` is available and will return `nil` during initialization. |
-| `set(version:)` | The `WhatsNew.Version` will be set after the `CompletionButton` has been pressed. |
+### WhatsNew.Feature
-The `WhatsNewVersionStore` can be passed as an parameter to the initializer. If you do so the initializer will become `optional`.
+A `WhatsNew.Feature` describe a specific feature of your app and generally consist of an image, title, and subtitle.
```swift
-// Initialize WhatsNewViewController with WhatsNewVersionStore
-let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
- whatsNew: whatsNew,
- versionStore: myVersionStore
+let feature = WhatsNew.Feature(
+ image: .init(
+ systemName: "wand.and.stars"
+ ),
+ title: "New Design",
+ subtitle: .init(
+ try AttributedString(
+ markdown: "An awesome new _Design_"
+ )
+ )
)
-
-// Check if WhatsNewViewController is available to present it.
-if let controller = whatsNewViewController {
- // Present it as WhatsNewViewController is available
- // after init with WhatsNewVersionStore
- self.present(controller, animated: true)
-} else {
- // WhatsNewViewController is `nil` this Version has already been presented
-}
-
-// Or invoke present on the WhatsNewViewController
-// to avoid the need of unwrapping the optional
-whatsNewViewController?.present(on: self)
```
-> ☝️ Please keep in mind the `WhatsNewViewController` initializer will only become `optional` and checks if the Version has been already presented if you pass a `WhatsNewVersionStore` object.
+### WhatsNew.PrimaryAction
-#### Implementation
-If you already handled saving user settings in your app to something like `Realm`, `CoreData` or `UserDefaults` you can conform that to the `WhatsNewVersionStore`.
+The `WhatsNew.PrimaryAction` allows you to configure the behaviour of the primary button which is used to dismiss the presented `WhatsNewView`
```swift
-// Extend your existing App-Logic
-extension MyUserSettingsDatabase: WhatsNewVersionStore {
- // Implement me 👨💻
-}
+let primaryAction = WhatsNew.PrimaryAction(
+ title: "Continue",
+ backgroundColor: .blue,
+ foregroundColor: .white,
+ hapticFeedback: .notification(.success),
+ onDismiss: {
+ print("WhatsNewView has been dismissed")
+ }
+)
```
+> Note: HapticFeedback will only be executed on iOS
-#### Predefined Implementations
-
-`WhatsNewKit` brings along two predefined Implementations of the `WhatsNewVersionStore`.
+### WhatsNew.SecondaryAction
-##### KeyValueWhatsNewVersionStore
-
-The [KeyValueWhatsNewVersionStore](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Store/KeyValueWhatsNewVersionStore.swift) saves and retrieves the `WhatsNew.Version` via a `KeyValueable` protocol conform object. `UserDefaults` and `NSUbiquitousKeyValueStore` are already conform to that protocol 🙌
+A `WhatsNew.SecondaryAction` which is displayed above the `WhatsNew.PrimaryAction` can be optionally supplied when initializing a `WhatsNew` instance and allows you to present an additional View, perform a custom action or open an URL.
```swift
-// Local KeyValueStore
-let keyValueVersionStore = KeyValueWhatsNewVersionStore(
- keyValueable: UserDefaults.standard
+// SecondaryAction that presents a View
+let secondaryActionPresentAboutView = WhatsNew.SecondaryAction(
+ title: "Learn more",
+ foregroundColor: .blue,
+ hapticFeedback: .selection,
+ action: .present {
+ AboutView()
+ }
)
-// iCloud KeyValueStore
-let keyValueVersionStore = KeyValueWhatsNewVersionStore(
- keyValueable: NSUbiquitousKeyValueStore.default
+// SecondaryAction that opens a URL
+let secondaryActionOpenURL = WhatsNew.SecondaryAction(
+ title: "Read more",
+ foregroundColor: .blue,
+ hapticFeedback: .selection,
+ action: .open(
+ url: .init(string: "https://github.com/SvenTiigi/WhatsNewKit")
+ )
)
-// Initialize WhatsNewViewController with KeyValueWhatsNewVersionStore
-let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
- whatsNew: whatsNew,
- versionStore: keyValueVersionStore
+// SecondaryAction with custom execution
+let secondaryActionCustom = WhatsNew.SecondaryAction(
+ title: "Custom",
+ action: .custom { presentationMode in
+ // ...
+ }
)
```
+> Note: HapticFeedback will only be executed on iOS
+
+## Layout
-##### InMemoryWhatsNewVersionStore
+WhatsNewKit allows you to adjust the layout of a presented `WhatsNewView` in various ways.
-The [InMemoryWhatsNewVersionStore](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Store/InMemoryWhatsNewVersionStore.swift) saves and retrieves the `WhatsNew.Version` in memory. Perfect for development or testing phase 👨💻
+The most simple way is by mutating the `WhatsNew.Layout.default` instance.
```swift
-// Initialize WhatsNewViewController with InMemoryWhatsNewVersionStore
-let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
- whatsNew: whatsNew,
- versionStore: InMemoryWhatsNewVersionStore()
-)
+WhatsNew.Layout.default.featureListSpacing = 35
```
-#### WhatsNew.Version
-During the initialization of the `WhatsNew` struct the `WhatsNewKit` will automatically retrieve the current App-Version via the [CFBundleShortVersionString](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html) and construct a [WhatsNew.Version](https://github.com/SvenTiigi/WhatsNewKit/blob/master/Sources/Models/WhatsNew%2BVersion.swift) for you which is used by the `WhatsNewVersionStore` protocol in order to persist the presented app versions. If you want to manually set the version you can do it like the following example.
+When using the automatic presentation style you can supply a default layout when initializing the WhatsNewEnvironment.
```swift
-// Initialize Version 1.0.0
-let version = WhatsNew.Version(
- major: 1,
- minor: 0,
- patch: 0
+.environment(
+ \.whatsNew,
+ .init(
+ defaultLayout: WhatsNew.Layout(
+ showsScrollViewIndicators: true,
+ featureListSpacing: 35
+ ),
+ whatsNew: self
+ )
)
-
-// Use a String literal
-let version: WhatsNew.Version = "1.0.0"
-
-// Current Version in Bundle (Default)
-let version = WhatsNew.Version.current()
```
-After you initialize a `WhatsNew.Version` you can pass it to the initializer of a `WhatsNew` struct.
+Alternatively you can pass a `WhatsNew.Layout` when automatically or manually presenting the WhatsNewView
```swift
-// Initialize WhatsNew with Title and Items
-let whatsNew = WhatsNew(
- version: version,
- title: "WhatsNewKit",
- items: []
+.whatsNewSheet(
+ layout: WhatsNew.Layout(
+ contentPadding: .init(
+ top: 80,
+ leading: 0,
+ bottom: 0,
+ trailing: 0
+ )
+ )
)
```
-If you holding multiple `WhatsNew` structs in an array you can make use of the following two functions to retrieve a `WhatsNew` struct based on the `WhatsNewVersion`.
-
```swift
-let whatsNews: [WhatsNew] = [...]
-
-// Retrieve WhatsNew from array based on Version 1.0.0
-let whatsNewVersion1 = whatsNews.get(byVersion:
- .init(major: 1, minor: 0, patch: 0)
+.sheet(
+ whatsNew: self.$whatsNew,
+ layout: WhatsNew.Layout(
+ footerActionSpacing: 20
+ )
)
+```
-// Or retrieve it via String as WhatsNew.Version is
-// conform to the ExpressibleByStringLiteral protocol
-let whatsNewVersion2 = whatsNews.get(byVersion: "2.0.0")
+## WhatsNewViewController
-// If you want the WhatsNew for your current App-Version
-// based on the CFBundleShortVersionString from Bundle.main
-let currentWhatsNew = whatsNews.get()
+When using `UIKit` or `AppKit` you can make use of the `WhatsNewViewController`.
+```swift
+let whatsNewViewController = WhatsNewViewController(
+ whatsNew: WhatsNew(
+ version: "1.0.0",
+ // ...
+ ),
+ layout: WhatsNew.Layout(
+ contentSpacing: 80
+ )
+)
```
-### Codable WhatsNew
-The `WhatsNew` struct is conform the `Codable` protocol which allows you to initialize a `WhatsNew` struct via `JSON`.
-
-```JSON
-{
- "version": {
- "major": 1,
- "minor": 0,
- "patch": 0
- },
- "title": "WhatsNewKit",
- "items": [
- {
- "title": "Open Source",
- "subtitle": "Contributions are very welcome 👨💻",
- "image": "iVBORw0KGgoA..."
- }
- ]
-}
-```
-The optional `image` property of the `WhatsNew.Item` will be decoded and encoded in [Base64](https://en.wikipedia.org/wiki/Base64).
+If you wish to present a `WhatsNewViewController` only if the version of the WhatsNew instance has not been presented you can make use of the convenience failable initializer.
```swift
-// Encode to JSON
-let encoded = try? JSONEncoder().encode(whatsNew)
+// Verify WhatsNewViewController is available for presentation
+guard let whatsNewViewController = WhatsNewViewController(
+ whatsNew: WhatsNew(
+ version: "1.0.0",
+ // ...
+ ),
+ versionStore: UserDefaultsWhatsNewVersionStore()
+) else {
+ // Version of WhatsNew has already been presented
+ return
+}
-// Decode from JSON data
-let decoded = try? JSONDecoder().decode(WhatsNew.self, from: data)
+// Present WhatsNewViewController
+// Version will be automatically saved in the provided
+// WhatsNewVersionStore when the WhatsNewViewController gets dismissed
+self.present(whatsNewViewController, animated: true)
```
-## Featured on
-
-* [Awesome iOS Weekly](http://weekly.awesomeios.com/issues/2#start)
-* [Swift Weekly](http://digest.swiftweekly.com/issues/swift-weekly-issue-118-114740)
-* [AppCoda Weekly](http://digest.appcoda.com/issues/appcoda-weekly-issue-74-116292)
-* [iOS Goodies](http://ios-goodies.com/post/174437386181/week-232)
-* [MyBridge - Open Source Swift Projects (June 2018)](https://medium.mybridge.co/swift-open-source-projects-of-the-month-v-june-2018-f9ce1239eee4)
-* [The iOS Times](http://theiostimes.com/issue-44.html)
-* [DZone](https://dzone.com/articles/this-week-in-mobile-11-june-2018)
-* [Brian Advent](https://youtu.be/zCHEpN1Wgz4)
-* [23 Amazing iOS UI Libraries written in Swift for the Past Year (v.2019)](https://medium.mybridge.co/23-amazing-ios-ui-libraries-written-in-swift-for-the-past-year-v-2019-3e5456318768)
-* [Indie iOS Focus Weekly](https://indieiosfocus.com/issues/234)
-* [5 iOS Libraries That Will Inspire Your Creativity](https://medium.com/better-programming/5-ios-libraries-that-will-inspire-your-creativity-26ee5837f9b7)
-
-## Contributing
-Contributions are very welcome 🙌 🤓
-
-## Credits
-The `WhatsNew.Item` images ([icons8-github](https://icons8.com/icon/62856/github), [icons8-puzzle](https://icons8.com/icon/61018/puzzle), [icons8-approval](https://icons8.com/icon/59733/approval), [icons8-picture](https://icons8.com/icon/68826/picture)) which are seen on the screenshots and inside the example application are taken from [icons8.com](https://icons8.com/) which are licensed under [Creative Commons Attribution-NoDerivs 3.0 Unported](https://creativecommons.org/licenses/by-nd/3.0/).
-
## License
```
WhatsNewKit
-Copyright (c) 2020 Sven Tiigi
+Copyright (c) 2022 Sven Tiigi sven.tiigi@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Sources/Collection/WhatsNewCollection.swift b/Sources/Collection/WhatsNewCollection.swift
new file mode 100644
index 0000000..2333e8b
--- /dev/null
+++ b/Sources/Collection/WhatsNewCollection.swift
@@ -0,0 +1,4 @@
+import Foundation
+
+/// A WhatsNewCollection type representing an array of WhatsNew elements
+public typealias WhatsNewCollection = [WhatsNew]
diff --git a/Sources/Collection/WhatsNewCollectionBuilder.swift b/Sources/Collection/WhatsNewCollectionBuilder.swift
new file mode 100644
index 0000000..ceabecb
--- /dev/null
+++ b/Sources/Collection/WhatsNewCollectionBuilder.swift
@@ -0,0 +1,53 @@
+import Foundation
+
+// MARK: - WhatsNewCollectionBuilder
+
+/// A WhatsNewCollectionBuilder
+@resultBuilder
+public enum WhatsNewCollectionBuilder {}
+
+// MARK: - ResultBuilder
+
+public extension WhatsNewCollectionBuilder {
+
+ /// Build WhatsNewCollection
+ /// - Parameter components: The WhatsNew elements
+ static func buildBlock(
+ _ components: WhatsNewCollection.Element?...
+ ) -> WhatsNewCollection {
+ components.compactMap { $0 }
+ }
+
+ /// Build optional WhatsNewCollection
+ /// - Parameter component: The optional WhatsNewCollection
+ static func buildOptional(
+ _ component: WhatsNewCollection?
+ ) -> WhatsNewCollection {
+ component ?? .init()
+ }
+
+ /// Build either first WhatsNewCollection
+ /// - Parameter component: The first WhatsNewCollection
+ static func buildEither(
+ first component: WhatsNewCollection
+ ) -> WhatsNewCollection {
+ component
+ }
+
+ /// Build either second WhatsNewCollection
+ /// - Parameter component: The second WhatsNewCollection
+ static func buildEither(
+ second component: WhatsNewCollection
+ ) -> WhatsNewCollection {
+ component
+ }
+
+ /// Build array of WhatsNewCollections
+ /// - Parameter components: The array of WhatsNewCollections
+ static func buildArray(
+ _ components: [WhatsNewCollection]
+ ) -> WhatsNewCollection {
+ components.flatMap { $0 }
+ }
+
+}
diff --git a/Sources/Collection/WhatsNewCollectionProvider.swift b/Sources/Collection/WhatsNewCollectionProvider.swift
new file mode 100644
index 0000000..57aef57
--- /dev/null
+++ b/Sources/Collection/WhatsNewCollectionProvider.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+// MARK: - WhatsNewProvider
+
+/// A WhatsNewCollection Provider type
+public protocol WhatsNewCollectionProvider {
+
+ /// A WhatsNewCollection
+ @WhatsNewCollectionBuilder
+ var whatsNewCollection: WhatsNewCollection { get }
+
+}
diff --git a/Sources/Components/Buttons/WhatsNewButtonViewController+CompletionButton.swift b/Sources/Components/Buttons/WhatsNewButtonViewController+CompletionButton.swift
deleted file mode 100644
index ce9cb64..0000000
--- a/Sources/Components/Buttons/WhatsNewButtonViewController+CompletionButton.swift
+++ /dev/null
@@ -1,175 +0,0 @@
-//
-// WhatsNewButtonViewController+CompletionButton.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - CompletionButton
-
-extension WhatsNewButtonViewController {
-
- /// The CompletionButton
- final class CompletionButton: UIButton {
-
- // MARK: Properties
-
- /// The highlighted background color
- var highlightedBackgroundColor: UIColor = .white
-
- /// The onPress closure
- var onPress: (() -> Void)?
-
- // MARK: Initializer
-
- /// Convenience initializer
- ///
- /// - Parameters:
- /// - title: The title
- /// - configuration: The Configuration
- /// - onPress: The on press closure
- convenience init(
- title: String,
- configuration: WhatsNewViewController.Configuration,
- onPress: @escaping () -> Void
- ) {
- // Init with Custom Type
- self.init(type: .custom)
- // Set onPress closure
- self.onPress = onPress
- // Set highlighted background color
- self.highlightedBackgroundColor = configuration.completionButton.backgroundColor
- // Set title
- self.setTitle(title, for: .normal)
- // Perform configuration
- self.configure(completionButton: configuration.completionButton)
- }
-
- // MARK: View-Lifecycle
-
- /// Layout Subviews
- override func layoutSubviews() {
- super.layoutSubviews()
- // Check if current background image is nil
- if self.currentBackgroundImage == nil {
- // Apply background color
- self.applyBackgroundColor()
- }
- }
-
- /// TraitCollection did change
- /// - Parameter previousTraitCollection: The previous TraitCollection
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
- // Apply background color
- self.applyBackgroundColor()
- }
-
- }
-
-}
-
-// MARK: - Configure
-
-extension WhatsNewButtonViewController.CompletionButton {
-
- /// Configure with CompletionButton configuraton
- ///
- /// - Parameter completionButton: The CompletionButton Configuration
- func configure(
- completionButton: WhatsNewViewController.CompletionButton
- ) {
- // Set corner radius to rounded button
- self.layer.cornerRadius = completionButton.cornerRadius
- // Set mask to bound
- self.layer.masksToBounds = true
- // Set Content EdgeInsets
- self.contentEdgeInsets = completionButton.contentEdgeInsets
- // Set font
- self.titleLabel?.font = completionButton.titleFont
- // Set normal title color
- self.setTitleColor(completionButton.titleColor, for: .normal)
- // Set number of lines
- self.titleLabel?.numberOfLines = 0
- // Set line break mode
- self.titleLabel?.lineBreakMode = .byWordWrapping
- // Add target
- self.addTarget(
- self,
- action: #selector(self.didTouchUpInside),
- for: .touchUpInside
- )
- }
-
-}
-
-// MARK: - Apply Background Color
-
-extension WhatsNewButtonViewController.CompletionButton {
-
- /// Apply background color
- func applyBackgroundColor() {
- // Set the backgroundimage
- self.setBackgroundImage(
- .from(
- self.highlightedBackgroundColor,
- size: self.bounds.size
- ),
- for: .normal
- )
- }
-
-}
-
-// MARK: - Target Handler
-
-extension WhatsNewButtonViewController.CompletionButton {
-
- /// Button did touch up inside
- @objc
- func didTouchUpInside() {
- // Invoke onPress closure
- self.onPress?()
- }
-
-}
-
-// MARK: - UIImage+From
-
-private extension UIImage {
-
- /// Return a UIImage with a given UIColor and CGSize
- ///
- /// - Parameters:
- /// - color: The color of the returned UIImage
- /// - size: The size of the returned UIImage. Default value `width: 1, height: 1`
- /// - Returns: Optional UIImage
- static func from(
- _ color: UIColor,
- size: CGSize = .init(width: 1, height: 1)
- ) -> UIImage? {
- // Verify that the size is not equal to zero
- guard size != .zero else {
- // Otherwise return nil
- return nil
- }
- // Initialize rect
- let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
- // Begin Graphics Context
- UIGraphicsBeginImageContextWithOptions(size, false, 0)
- // Set fill color
- color.setFill()
- // Fill rect with color
- UIRectFill(rect)
- // Retrieve Image from Graphics Context
- let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
- // End Graphics Context
- UIGraphicsEndImageContext()
- // Return image
- return image
- }
-
-}
diff --git a/Sources/Components/Buttons/WhatsNewButtonViewController+DetailButton.swift b/Sources/Components/Buttons/WhatsNewButtonViewController+DetailButton.swift
deleted file mode 100644
index e0fed3c..0000000
--- a/Sources/Components/Buttons/WhatsNewButtonViewController+DetailButton.swift
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-// WhatsNewButtonViewController+DetailButton.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - DetailButton
-
-extension WhatsNewButtonViewController {
-
- /// The DetailButton
- final class DetailButton: UIButton {
-
- // MARK: Properties
-
- /// The onPress closure
- let onPress: () -> Void
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - detailButton: The DetailButton configuration
- /// - onPress: The on press closure
- init(
- detailButton: WhatsNewViewController.DetailButton?,
- onPress: @escaping () -> Void
- ) {
- // Set on press
- self.onPress = onPress
- // Super init
- super.init(frame: .zero)
- // Configure DetailButton
- self.configure(detailButton: detailButton)
- }
-
- /// Initializer with Coder always returns nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- }
-
-}
-
-// MARK: - Configure
-
-extension WhatsNewButtonViewController.DetailButton {
-
- /// Configure with DetailButton Configuration
- ///
- /// - Parameter detailButton: The DetailButton Configuration
- func configure(
- detailButton: WhatsNewViewController.DetailButton?
- ) {
- // Set title
- self.setTitle(
- detailButton?.title,
- for: .normal
- )
- // Add target
- self.addTarget(
- self,
- action: #selector(self.didTouchUpInside),
- for: .touchUpInside
- )
- // Set title color
- self.setTitleColor(
- detailButton?.titleColor,
- for: .normal
- )
- // Set font
- self.titleLabel?.font = detailButton?.titleFont
- }
-
-}
-
-// MARK: - Target Handler
-
-extension WhatsNewButtonViewController.DetailButton {
-
- /// Did touch up inside
- @objc
- func didTouchUpInside() {
- // Invoke on press closure
- self.onPress()
- }
-
-}
diff --git a/Sources/Components/Buttons/WhatsNewButtonViewController.swift b/Sources/Components/Buttons/WhatsNewButtonViewController.swift
deleted file mode 100644
index 8bad324..0000000
--- a/Sources/Components/Buttons/WhatsNewButtonViewController.swift
+++ /dev/null
@@ -1,177 +0,0 @@
-//
-// WhatsNewButtonViewController.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - WhatsNewButtonViewController
-
-/// The WhatsNewButtonViewController
-final class WhatsNewButtonViewController: UIViewController {
-
- // MARK: Properties
-
- /// The Configuration
- var configuration: WhatsNewViewController.Configuration
-
- /// The onPress closure
- let onPress: (ButtonType) -> Void
-
- /// The completion Button
- lazy var completionButton: UIButton = CompletionButton(
- title: self.configuration.completionButton.title,
- configuration: self.configuration,
- onPress: { [weak self] in
- // Invoke on press with completion button type
- self?.onPress(.completion)
- }
- )
-
- /// The detail button
- lazy var detailButton: UIButton = DetailButton(
- detailButton: self.configuration.detailButton,
- onPress: { [weak self] in
- // Invoke on press with detail button type
- self?.onPress(.detail)
- }
- )
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - configuration: The Configuration
- /// - onPress: The onPress closure with ButtonType
- init(
- configuration: WhatsNewViewController.Configuration,
- onPress: @escaping (ButtonType) -> Void
- ) {
- self.configuration = configuration
- self.onPress = onPress
- super.init(nibName: nil, bundle: nil)
- // Hide View if an animation is available
- self.view.isHidden = self.configuration.detailButton?.animation != nil
- || self.configuration.completionButton.animation != nil
- }
-
- /// Initializer with Coder always return nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- // MARK: View-Lifecycle
-
- /// View did load
- override func viewDidLoad() {
- super.viewDidLoad()
- // Set background color
- self.view.backgroundColor = self.configuration.backgroundColor
- // Add Subviews
- self.addSubviews()
- // Make Constraints
- self.makeConstraints()
- }
-
- /// View did appear
- ///
- /// - Parameter animated: If should be animated
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- // Disable isHidden
- self.view.isHidden = false
- // Perform animation if available
- self.configuration.detailButton?.animation?.rawValue(
- self.detailButton,
- .init(
- preferredDuration: 0.5,
- preferredDelay: 0.3
- )
- )
- // Perform animation if available
- self.configuration.completionButton.animation?.rawValue(
- self.completionButton,
- .init(
- preferredDuration: 0.5,
- preferredDelay: 0.4
- )
- )
- // Clear Animations
- self.configuration.detailButton?.animation = nil
- self.configuration.completionButton.animation = nil
- }
-
-}
-
-// MARK: - Add Subviews
-
-extension WhatsNewButtonViewController {
-
- /// Add Subviews
- func addSubviews() {
- // Check if a DetailButton Configuration is available
- if self.configuration.detailButton != nil {
- // Add DetailButton to Subview
- self.view.addSubview(self.detailButton)
- }
- // Add CompletionButton to Subview
- self.view.addSubview(self.completionButton)
- }
-
-}
-
-// MARK: - Make Constraints
-
-extension WhatsNewButtonViewController {
-
- /// Make Constraints
- func makeConstraints() {
- // Declare the CompletionButtonTopAnchor
- let completionButtonTopAnchor: NSLayoutConstraint
- // Check if a DetailButton Configuration is available
- if let detailButton = self.configuration.detailButton {
- // Make Constraints on DetailButton
- self.detailButton.makeConstraints(
- self.detailButton.topAnchor.constraint(equalTo: self.anchor.topAnchor),
- self.detailButton.leadingAnchor.constraint(equalTo: self.anchor.leadingAnchor),
- self.detailButton.trailingAnchor.constraint(equalTo: self.anchor.trailingAnchor)
- )
- // Initialize TopAnchor
- completionButtonTopAnchor = self.completionButton.topAnchor.constraint(
- equalTo: self.detailButton.bottomAnchor,
- constant: detailButton.bottomOffset
- )
- } else {
- // Initialize TopAnchor
- completionButtonTopAnchor = self.completionButton.topAnchor.constraint(
- equalTo: self.anchor.topAnchor
- )
- }
- // Make Constraints on CompletionButton
- self.completionButton.makeConstraints(
- completionButtonTopAnchor,
- self.completionButton.leadingAnchor.constraint(equalTo: self.anchor.leadingAnchor),
- self.completionButton.trailingAnchor.constraint(equalTo: self.anchor.trailingAnchor),
- self.completionButton.bottomAnchor.constraint(equalTo: self.anchor.bottomAnchor)
- )
- }
-
-}
-
-// MARK: - ButtonType
-
-extension WhatsNewButtonViewController {
-
- /// The ButtonType
- enum ButtonType: String, Codable, Equatable, Hashable, CaseIterable {
- /// Detail button
- case detail
- /// Completion button
- case completion
- }
-
-}
diff --git a/Sources/Components/Items/WhatsNewItemsViewController+Cell.swift b/Sources/Components/Items/WhatsNewItemsViewController+Cell.swift
deleted file mode 100644
index 258eb77..0000000
--- a/Sources/Components/Items/WhatsNewItemsViewController+Cell.swift
+++ /dev/null
@@ -1,295 +0,0 @@
-//
-// WhatsNewItemsViewController+Cell.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - Cell
-
-extension WhatsNewItemsViewController {
-
- /// The Cell
- final class Cell: UITableViewCell {
-
- // MARK: Properties
-
- /// The Item
- let item: WhatsNew.Item
-
- /// The Configuration
- let configuration: WhatsNewViewController.Configuration
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - item: The WhatsNew Item
- /// - configuration: The Configuration
- init(
- item: WhatsNew.Item,
- configuration: WhatsNewViewController.Configuration
- ) {
- // Set item
- self.item = item
- // Set configuration
- self.configuration = configuration
- // Super init default style
- super.init(
- style: .default,
- reuseIdentifier: String(describing: Cell.self)
- )
- // Set background color
- self.contentView.backgroundColor = self.configuration.backgroundColor
- // Configure
- self.configure()
- }
-
- /// Initializer with Coder always returns nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- // MARK: View-Lifecycle
-
- /// TraitCollection did change
- /// - Parameter previousTraitCollection: The previous TraitCollection
- override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
- super.traitCollectionDidChange(previousTraitCollection)
- // Re-Configure
- self.configure()
- }
-
- }
-
-}
-
-// MARK: - Configure
-
-extension WhatsNewItemsViewController.Cell {
-
- /// Perform configuration
- func configure() {
- // Check if ItemsView Layout is not eqaul to centered
- if self.configuration.itemsView.layout != .centered {
- // Perform ImageView Configuration
- self.configureImageView()
- }
- // Perform TextLabel Configuration
- self.configureTextLabel()
- }
-
-}
-
-// MARK: - Configure TextLabel
-
-extension WhatsNewItemsViewController.Cell {
-
- /// Configure TextLabel
- func configureTextLabel() {
- // Set font
- self.textLabel?.font = self.configuration.itemsView.subtitleFont
- // Set text color
- self.textLabel?.textColor = self.configuration.itemsView.subtitleColor
- // Set number of lines to zero
- self.textLabel?.numberOfLines = 0
- // Set line break mode to word wrapping
- self.textLabel?.lineBreakMode = .byWordWrapping
- // Set attributed text
- self.textLabel?.attributedText = self.makeAttributedTextString()
- // Set white background color
- self.backgroundColor = .white
- // Check if Layout is centered or right
- if self.configuration.itemsView.layout == .centered {
- // Set centered Text
- self.textLabel?.textAlignment = .center
- } else if self.configuration.itemsView.layout == .right {
- // Set Text to right
- self.textLabel?.textAlignment = .right
- }
- }
-
-}
-
-// MARK: - Configure ImageView
-
-extension WhatsNewItemsViewController.Cell {
-
- /// Configure ImageView
- func configureImageView() {
- // Enable scale aspect fit
- self.imageView?.contentMode = .scaleAspectFit
- // Clip to Bounds
- self.imageView?.clipsToBounds = true
- // Declare an optional UIImage
- var image: UIImage?
- // Switch on ImageSize
- switch self.configuration.itemsView.imageSize {
- case .original:
- // Initialize Image with Item Image
- image = self.item.image
- case .fixed(let height):
- // Initialize Image with resized Item Image
- image = self.item.image?.resizeToSquare(ofSize: .init(height))
- }
- // Check if autoTintImage is activated
- if self.configuration.itemsView.autoTintImage {
- // Tint image with tint color
- image = image?.tint(color: self.configuration.tintColor)
- }
- // Set image
- self.imageView?.image = image
- }
-
-}
-
-// MARK: - Make AttributedString
-
-extension WhatsNewItemsViewController.Cell {
-
- /// Make AttributedString Text String
- ///
- /// - Returns: The Attributed String
- func makeAttributedTextString() -> NSAttributedString {
- // Declare Title Subtitle Attributed String
- let titleSubtitleAttributedString: NSMutableAttributedString
- // Check if title is empty
- if self.item.title.isEmpty {
- // Just return the item subtitle has no title is available
- titleSubtitleAttributedString = .init(string: self.item.subtitle)
- } else {
- // Initialize attributed string
- titleSubtitleAttributedString = .init(string: "\(self.item.title)\n\(self.item.subtitle)")
- // Initialize Range
- let range = NSRange(location: 0, length: self.item.title.utf16.count)
- // Add title font
- titleSubtitleAttributedString.addAttributes(
- [.font: self.configuration.itemsView.titleFont],
- range: range
- )
- // Add title color
- titleSubtitleAttributedString.addAttributes(
- [.foregroundColor: self.configuration.itemsView.titleColor],
- range: range
- )
- }
- // Check if Layout is centered
- if self.configuration.itemsView.layout == .centered {
- // Initialize a TextAttachment
- let attachment = NSTextAttachment()
- // Set Attachment Image
- attachment.image = self.item.image
- // Check if ItemsView should auto tint image
- if self.configuration.itemsView.autoTintImage,
- let tintedImage = attachment.image?.tint(color: self.configuration.tintColor) {
- // Set tinted image
- attachment.image = tintedImage
- }
- // Add Line Break at first position to TitleSubtitle String to create a spacing between Image and Text
- titleSubtitleAttributedString.insert(.init(string: "\n\n"), at: 0)
- // Initialize Attachment String
- let attachmentString = NSAttributedString(attachment: attachment)
- // Insert Attachment String at first position
- titleSubtitleAttributedString.insert(attachmentString, at: 0)
- }
- // Return TitleSubtitle String
- return titleSubtitleAttributedString
- }
-
-}
-
-// MARK: - UIImage+resizeToSquare
-
-private extension UIImage {
-
- /// Resize UIImage to a square of the specified dimensions, preserving the aspect ratio.
- ///
- /// - Parameter ofSize: The size of the height and width of the destination square.
- /// - Returns: The resized Image
- func resizeToSquare(ofSize newSize: CGFloat) -> UIImage? {
- // Verify the size is greater zero
- guard newSize > 0 else {
- // Otherwise return nil
- return nil
- }
- // The returned image will be a square of the specified size
- let containerSize = CGSize(width: newSize, height: newSize)
- // The target size is the size of the image portion within the container. We preserve the aspect ratio, and ensure
- // that it touches the container edges either on the left & right sides, or top & bottom (or both).
- let targetSize: CGSize
- if self.size.width > self.size.height {
- targetSize = .init(
- width: newSize,
- height: (newSize / self.size.width) * self.size.height
- )
- } else {
- targetSize = .init(
- width: (newSize / self.size.height) * self.size.width,
- height: newSize
- )
- }
- // The origin for our redrawing, within the container, can be obtained by getting the difference between the container's
- // dimensions and the target size dimensions. Divided by 2,
- // since we want the redrawn image to be centered in the container.
- let redrawOrigin = CGPoint(
- x: (containerSize.width - targetSize.width) / 2,
- y: (containerSize.height - targetSize.height) / 2
- )
- // Check if iOS 10 or greater is available
- if #available(iOS 10.0, *) {
- // Return rendererd Image with target size
- return UIGraphicsImageRenderer(size: containerSize).image { [weak self] _ in
- // Draw with target size
- self?.draw(in: .init(origin: redrawOrigin, size: targetSize))
- }
- } else {
- // Begin Image Context with target size
- UIGraphicsBeginImageContextWithOptions(containerSize, false, 0.0)
- // Draw Image with target size
- self.draw(in: .init(origin: redrawOrigin, size: targetSize))
- // Retrive Image from context
- let image = UIGraphicsGetImageFromCurrentImageContext()
- // End context
- UIGraphicsEndImageContext()
- // Return resized image
- return image
- }
- }
-
-}
-
-// MARK: - UIImage+tint
-
-private extension UIImage {
-
- /// Tint Image with Color
- ///
- /// - Parameter color: The Color
- /// - Returns: The tinted UIImage
- func tint(color: UIColor) -> UIImage? {
- // Verify that the size is not equal to zero
- guard self.size != .zero else {
- // Otherwise return untinted image
- return self
- }
- // Retrieve image as template image
- let image = self.withRenderingMode(.alwaysTemplate)
- // Begin Image Context
- UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
- // Set color
- color.set()
- // Draw image with size
- image.draw(in: .init(origin: .zero, size: self.size))
- // Retrieve Image from context
- let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
- // End context
- UIGraphicsEndImageContext()
- // Return tinted image
- return tintedImage
- }
-
-}
diff --git a/Sources/Components/Items/WhatsNewItemsViewController.swift b/Sources/Components/Items/WhatsNewItemsViewController.swift
deleted file mode 100644
index 2828758..0000000
--- a/Sources/Components/Items/WhatsNewItemsViewController.swift
+++ /dev/null
@@ -1,420 +0,0 @@
-//
-// WhatsNewItemsViewController.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - WhatsNewItemsViewController
-
-/// The WhatsNewItemsViewController
-final class WhatsNewItemsViewController: UIViewController {
-
- // MARK: Properties
-
- /// The WhatsNew Items
- let items: [WhatsNew.Item]
-
- /// The Configuration
- let configuration: WhatsNewViewController.Configuration
-
- /// The NotificationCenter
- let notificationCenter: NotificationCenter
-
- /// The cell display count
- var cellDisplayCount = 0
-
- /// Bool if animation is disabled
- var isAnimationDisabled: Bool = false
-
- /// The TableView
- lazy var tableView: UITableView = {
- // Initialize TableView
- let tableView = UITableView(frame: .zero, style: .grouped)
- // Set clear background color
- tableView.backgroundColor = .clear
- // Set data source
- tableView.dataSource = self
- // Set delegate
- tableView.delegate = self
- // No seperators
- tableView.separatorStyle = .none
- // No selection
- tableView.allowsSelection = false
- // Hide Vertical Scroll Indicator
- tableView.showsVerticalScrollIndicator = false
- // Set indicator style based on theme backgroundcolor
- tableView.indicatorStyle = self.configuration.backgroundColor.isLight ? .black : .white
- // Set estimatedRowHeight so autosizing will work on iOS 10
- tableView.estimatedRowHeight = 44.0
- // Set automtic dimension for row height
- tableView.rowHeight = UITableView.automaticDimension
- // Check if ItemsView Layout is right
- if self.configuration.itemsView.layout == .right {
- // Set semantic content attribute to force right to left
- tableView.semanticContentAttribute = .forceRightToLeft
- }
- // Return TableView
- return tableView
- }()
-
- /// The Cells
- lazy var cells: [Cell] = self.items.map { item in
- .init(
- item: item,
- configuration: self.configuration
- )
- }
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - items: The WhatsNew Items
- /// - configuration: The Configuration
- /// - notificationCenter: The NotificationCenter. Default value `default`
- init(
- items: [WhatsNew.Item],
- configuration: WhatsNewViewController.Configuration,
- notificationCenter: NotificationCenter = .default
- ) {
- // Set items
- self.items = items
- // Set configuration
- self.configuration = configuration
- // Set NotificationCenter
- self.notificationCenter = notificationCenter
- // Super init
- super.init(nibName: nil, bundle: nil)
- // Hide View if an animation is available
- self.view.isHidden = self.configuration.itemsView.animation != nil
- }
-
- /// Initializer with Coder always return nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- /// Deinit
- deinit {
- // Remove Obser
- self.notificationCenter.removeObserver(self)
- }
-
- // MARK: View-Lifecycle
-
- /// View did load
- override func viewDidLoad() {
- super.viewDidLoad()
- // Set background color
- self.view.backgroundColor = self.configuration.backgroundColor
- // Add orientation did change observer
- self.notificationCenter.addObserver(
- self,
- selector: #selector(self.orientationDidChange),
- name: UIDevice.orientationDidChangeNotification,
- object: nil
- )
- }
-
- /// Load View
- override func loadView() {
- self.view = self.tableView
- }
-
- /// View did appear
- ///
- /// - Parameter animated: If should be animated
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- // Disable isHidden
- self.view.isHidden = false
- // Trigger reload data
- self.tableView.reloadData()
- }
-
- /// ScrollView will begin dragging
- ///
- /// - Parameter scrollView: The ScrollView
- func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
- // Set is animation disabled to true to improve the user
- // experience when having a large amount of Items
- self.isAnimationDisabled = true
- }
-
-}
-
-// MARK: - Notification Target
-
-extension WhatsNewItemsViewController {
-
- /// Orientation did change notification
- ///
- /// - Parameter notification: The Notification
- @objc
- func orientationDidChange(notification: Notification) {
- // Check if the Cell Display count is not zero
- if self.cellDisplayCount != 0 {
- // Set is animation disabled to true
- self.isAnimationDisabled = true
- }
- // Reload TableView
- self.tableView.reloadData()
- }
-
-}
-
-// MARK: - UITableViewDataSource
-
-extension WhatsNewItemsViewController: UITableViewDataSource {
-
- /// Retrieve number of sections
- ///
- /// - Parameter tableView: The TableView
- /// - Returns: Amount of section
- public func numberOfSections(
- in tableView: UITableView
- ) -> Int {
- // Return items count
- return self.items.count
- }
-
- /// Retrieve number of rows in section
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - section: The section
- /// - Returns: The amount of rows in section
- public func tableView(
- _ tableView: UITableView,
- numberOfRowsInSection section: Int
- ) -> Int {
- // Return one row
- return 1
- }
-
- /// Retrieve cell for row at IndexPath
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - indexPath: The IndexPath
- /// - Returns: The configured Cell
- public func tableView(
- _ tableView: UITableView,
- cellForRowAt indexPath: IndexPath
- ) -> UITableViewCell {
- // Initialize index
- let index = indexPath.section
- // Verify index is contained in indices
- guard self.cells.indices.contains(index) else {
- // Return unknown TableViewCell
- return UITableViewCell(
- style: .default,
- reuseIdentifier: "unknown"
- )
- }
- // Return Cell
- return self.cells[index]
- }
-
-}
-
-// MARK: - UITableViewDelegate
-
-extension WhatsNewItemsViewController: UITableViewDelegate {
-
- /// TableView will display cell
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - cell: The Cell
- /// - indexPath: The indexPath
- func tableView(
- _ tableView: UITableView,
- willDisplay cell: UITableViewCell,
- forRowAt indexPath: IndexPath
- ) {
- // Set background color
- cell.backgroundColor = self.configuration.backgroundColor
- // Verify view is not hidden
- guard !self.view.isHidden else {
- // Otherwise return out of function
- return
- }
- // Unwrap cell as Cell and verify cellDisplayCount is less then the items count
- guard let cell = cell as? Cell,
- !self.isAnimationDisabled,
- self.cellDisplayCount < self.items.count else {
- // Return out of function
- return
- }
- // Increment CellDisplayCount
- self.cellDisplayCount += 1
- // Animate Cell
- self.configuration.itemsView.animation?.rawValue(
- cell,
- .init(
- preferredDuration: 0.5,
- preferredDelay: 0.15 * (Double(indexPath.section) + 1.0)
- )
- )
- }
-
- /// Height for Header in section
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - section: The Section
- /// - Returns: The Header Height
- func tableView(
- _ tableView: UITableView,
- heightForHeaderInSection section: Int
- ) -> CGFloat {
- return self.calculateSpace(
- for: .header,
- in: section,
- contentMode: self.configuration.itemsView.contentMode
- )
- }
-
- /// Height for Footer in section
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - section: The Section
- /// - Returns: The Footer Height
- func tableView(
- _ tableView: UITableView,
- heightForFooterInSection section: Int
- ) -> CGFloat {
- return self.calculateSpace(
- for: .footer,
- in: section,
- contentMode: self.configuration.itemsView.contentMode
- )
- }
-
- /// View for Header in Section
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - section: The Section
- /// - Returns: The Header View
- func tableView(
- _ tableView: UITableView,
- viewForHeaderInSection section: Int
- ) -> UIView? {
- return nil
- }
-
- /// View for Footer in Section
- ///
- /// - Parameters:
- /// - tableView: The TableView
- /// - section: The Section
- /// - Returns: The Footer View
- func tableView(
- _ tableView: UITableView,
- viewForFooterInSection section: Int
- ) -> UIView? {
- return nil
- }
-
-}
-
-// MARK: - Calculate Space
-
-extension WhatsNewItemsViewController {
-
- /// The Position
- enum Position: String, Codable, Equatable, Hashable, CaseIterable {
- /// Header
- case header
- /// Footer
- case footer
- }
-
- /// Calculate Space for Position in Section
- ///
- /// - Parameters:
- /// - position: The Position
- /// - section: The Section
- /// - contentMode: The ContentMode
- /// - Returns: The calculcated Space
- func calculateSpace(
- for position: Position,
- in section: Int,
- contentMode: WhatsNewViewController.ItemsView.ContentMode
- ) -> CGFloat {
- // Declare Divider
- var divider: CGFloat
- // Switch on ContentMode
- switch contentMode {
- case .top:
- // Return zero Space
- return 0
- case .center:
- // Switch on position
- switch position {
- case .header:
- // Verify Section is first
- guard section == 0 else {
- // Otherwise return zero
- return 0
- }
- case .footer:
- // Verify Setion is last
- guard section == self.numberOfSections(in: self.tableView) - 1 else {
- // Otherwise return zero
- return 0
- }
- }
- // Initialize Divider
- divider = 2
- case .fill:
- // Check if Items count is one
- if self.items.count == 1 {
- // Return calculated Space with Center ContentMode
- return self.calculateSpace(
- for: position,
- in: section,
- contentMode: .center
- )
- }
- // Switch on position
- switch position {
- case .header:
- return 0
- case .footer:
- // Verify Setion is not last
- guard section != self.numberOfSections(in: self.tableView) - 1 else {
- // Otherwise return zero
- return 0
- }
- }
- // Initialize Divider
- divider = .init(self.numberOfSections(in: self.tableView) - 1)
- }
- // Check if divider is zero
- if divider == 0 {
- // Re-Initializer Divider with one
- divider = 1
- }
- // Map Cells to their frame size and add them together
- let height = self.cells.map { $0.frame.size.height }.reduce(0, +)
- // Initialize Space
- let space = (self.view.bounds.height - height) / divider
- // Verify Space is not negative
- guard space >= 10 else {
- // Otherwise return zero
- return 0
- }
- // Return space
- return space
- }
-
-}
diff --git a/Sources/Components/Title/WhatsNewTitleViewController.swift b/Sources/Components/Title/WhatsNewTitleViewController.swift
deleted file mode 100644
index a9fcc24..0000000
--- a/Sources/Components/Title/WhatsNewTitleViewController.swift
+++ /dev/null
@@ -1,133 +0,0 @@
-//
-// WhatsNewTitleViewController.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - WhatsNewTitleViewController
-
-/// The WhatsNewTitleViewController
-final class WhatsNewTitleViewController: UIViewController {
-
- // MARK: Properties
-
- /// The WhatsNew Title
- let titleText: String
-
- /// The Configuration
- var configuration: WhatsNewViewController.Configuration
-
- /// The title label
- lazy var titleLabel: UILabel = {
- let label = UILabel()
- label.backgroundColor = .clear
- label.numberOfLines = 0
- label.lineBreakMode = .byWordWrapping
- label.textAlignment = self.configuration.titleView.titleAlignment
- label.font = self.configuration.titleView.titleFont
- label.textColor = self.configuration.titleView.titleColor
- // Check if a secondary color is available
- if let secondaryColor = self.configuration.titleView.secondaryColor {
- // Set attributed text
- label.attributedText = .init(
- text: self.titleText,
- colorConfiguration: secondaryColor
- )
- } else {
- // No secondary color available simply set text
- label.text = self.titleText
- }
- return label
- }()
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - title: The Title
- /// - configuration: The Configuration
- init(
- title: String,
- configuration: WhatsNewViewController.Configuration
- ) {
- // Set title
- self.titleText = title
- // Set configuration
- self.configuration = configuration
- // Super init
- super.init(nibName: nil, bundle: nil)
- // Set background color
- self.view.backgroundColor = self.configuration.backgroundColor
- // Hide View if an animation is available
- self.view.isHidden = self.configuration.titleView.animation != nil
- }
-
- /// Initializer with Coder always return nil
- required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- // MARK: View-Lifecycle
-
- /// Load View
- override func loadView() {
- self.view = self.titleLabel
- }
-
- /// View did appear
- ///
- /// - Parameter animated: If should be animated
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- // Disable isHidden
- self.view.isHidden = false
- // Perform animation if available
- self.configuration.titleView.animation?.rawValue(
- self.titleLabel,
- .init(
- preferredDuration: 0.5,
- preferredDelay: 0.2
- )
- )
- // Clear Animation
- self.configuration.titleView.animation = nil
- }
-
-}
-
-// MARK: - NSAttributedString+Init
-
-private extension NSAttributedString {
-
- /// Convenience Initializer with Text and SecondaryColor Configuration
- ///
- /// - Parameters:
- /// - text: The Text
- /// - colorConfiguration: The SecondaryColor Configuration
- convenience init(
- text: String,
- colorConfiguration: WhatsNewViewController.TitleView.SecondaryColor
- ) {
- // Initialize NSMutableAttributedString with text
- let attributedString = NSMutableAttributedString(string: text)
- // Check if start index and length matches the string
- if text.dropFirst(colorConfiguration.startIndex).count >= colorConfiguration.length {
- // Add foreground color attribute
- attributedString.addAttributes(
- [.foregroundColor: colorConfiguration.color],
- range: .init(
- location: colorConfiguration.startIndex,
- length: colorConfiguration.length
- )
- )
- }
- // Init with AttributedString
- self.init(attributedString: attributedString)
- }
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+Animation.swift b/Sources/Configuration/WhatsNewViewController+Animation.swift
deleted file mode 100644
index b395c8b..0000000
--- a/Sources/Configuration/WhatsNewViewController+Animation.swift
+++ /dev/null
@@ -1,189 +0,0 @@
-//
-// WhatsNewViewController+Animation.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 06.06.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - Animation
-
-public extension WhatsNewViewController {
-
- /// The Animation
- enum Animation {
- /// Fade
- case fade
- /// Slide up
- case slideUp
- /// Slide down
- case slideDown
- /// Slide left
- case slideLeft
- /// Slide right
- case slideRight
- /// Custom Animation
- case custom(animator: Animator)
- }
-
-}
-
-// MARK: - Animator Equatable
-
-extension WhatsNewViewController.Animation: Equatable {
-
- /// Returns a Boolean value indicating whether two values are equal.
- ///
- /// - Parameters:
- /// - lhs: A value to compare.
- /// - rhs: Another value to compare.
- public static func == (
- lhs: WhatsNewViewController.Animation,
- rhs: WhatsNewViewController.Animation
- ) -> Bool {
- // Switch on left-hand and right-hand side
- switch (lhs, rhs) {
- case (.fade, .fade):
- return true
- case (.slideUp, .slideUp):
- return true
- case (.slideDown, .slideDown):
- return true
- case (.slideLeft, .slideLeft):
- return true
- case (.slideRight, .slideRight):
- return true
- case (.custom, .custom):
- return true
- default:
- return false
- }
- }
-
-}
-
-// MARK: - Animator Typealias
-
-public extension WhatsNewViewController.Animation {
-
- /// The Animator typealias closure with UIView and Item count
- typealias Animator = (UIView, AnimatorSettings) -> Void
-
- /// The AnimatorSettings
- struct AnimatorSettings: Codable, Equatable {
-
- /// The preferred duration
- public let preferredDuration: TimeInterval
-
- /// The preferred delay
- public let preferredDelay: TimeInterval
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - preferredDuration: The preferred duration
- /// - preferredDelay: The preferred delay
- public init(
- preferredDuration: TimeInterval,
- preferredDelay: TimeInterval
- ) {
- self.preferredDuration = preferredDuration
- self.preferredDelay = preferredDelay
- }
-
- }
-
-}
-
-// MARK: - Animator RawRepresentable
-
-extension WhatsNewViewController.Animation: RawRepresentable {
-
- /// Associated type RawValue as optional Animator
- public typealias RawValue = WhatsNewViewController.Animation.Animator
-
- /// RawRepresentable initializer
- ///
- /// - Parameters:
- /// - rawValue: The rawValue
- public init?(rawValue: @escaping RawValue) {
- self = .custom(animator: rawValue)
- }
-
- /// The optional Animator rawValue
- public var rawValue: RawValue {
- switch self {
- case .custom(animator: let animator):
- // Return custom animator
- return animator
- case .fade, .slideUp, .slideDown, .slideLeft, .slideRight:
- // Return predefined animation
- return self.animate
- }
- }
-
-}
-
-// MARK: Predefined Animation
-
-private extension WhatsNewViewController.Animation {
-
- /// Predefined Animation
- ///
- /// - Parameters:
- /// - view: The View
- /// - index: The Index
- func animate(
- view: UIView,
- animatorSettings: AnimatorSettings
- ) {
- // Declare Transform
- let transform: CGAffineTransform
- // Switch on self to initialize Transform
- switch self {
- case .slideUp:
- transform = .init(
- translationX: 0,
- y: view.frame.size.height / 2
- )
- case .slideDown:
- transform = .init(
- translationX: 0,
- y: view.frame.size.height / -2
- )
- case .slideLeft:
- transform = .init(
- translationX: view.frame.size.width,
- y: 0
- )
- case .slideRight:
- transform = .init(
- translationX: -view.frame.size.width,
- y: 0
- )
- default:
- transform = .identity
- }
- // Apply Transform
- view.transform = transform
- // Set zero alpha
- view.alpha = 0.0
- // Perform animation
- UIView.animate(
- // Duration
- withDuration: animatorSettings.preferredDuration,
- // Delay
- delay: animatorSettings.preferredDelay,
- // Ease in and out
- options: .curveEaseInOut,
- animations: {
- // Set identity transform
- view.transform = .identity
- // Set default alpha
- view.alpha = 1.0
- })
- }
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+CompletionButton.swift b/Sources/Configuration/WhatsNewViewController+CompletionButton.swift
deleted file mode 100644
index 6fcc921..0000000
--- a/Sources/Configuration/WhatsNewViewController+CompletionButton.swift
+++ /dev/null
@@ -1,136 +0,0 @@
-//
-// WhatsNewViewController+CompletionButton.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 27.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - CompletionButton
-
-public extension WhatsNewViewController {
-
- /// The CompletionButton
- struct CompletionButton: Equatable {
-
- /// The Title
- public var title: String
-
- /// The Action
- public var action: Action
-
- /// The HapticFeedback
- public var hapticFeedback: HapticFeedback?
-
- /// The background color
- public var backgroundColor: UIColor
-
- /// The title font
- public var titleFont: UIFont
-
- /// The title color
- public var titleColor: UIColor
-
- /// The corner radius
- public var cornerRadius: CGFloat
-
- /// The Animation
- public var animation: Animation?
-
- /// The Insets
- public var insets: UIEdgeInsets
-
- /// The Content Edge Insets
- public var contentEdgeInsets: UIEdgeInsets
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - title: The Title. Default value `Continue`
- /// - action: The Action. Default value `.dismiss`
- /// - hapticFeedback: The optional HapticFeedback. Default value `nil`
- /// - backgroundColor: The background color. Default value `.whatsNewKitBlue`
- /// - titleFont: The title font. Default value `size: 17, weight: semibold`
- /// - titleColor: The title color. Default value `white`
- /// - cornerRadius: The corner radius. Default value `14.0`
- /// - animation: The Animation. Default value `nil`
- /// - insets: The UIEdgeInsets. Default value `top: 5, left: 23.5, bottom: 53.5, right: 23.5`
- /// - contentEdgeInsets: The Content Edge UIEdgeInsets. Default value `top: 15, left: 0, bottom: 15, right: 0`
- public init(
- title: String = "Continue",
- action: Action = .dismiss,
- hapticFeedback: HapticFeedback? = nil,
- backgroundColor: UIColor = .whatsNewKitBlue,
- titleFont: UIFont = .systemFont(ofSize: 17, weight: .semibold),
- titleColor: UIColor = .white,
- cornerRadius: CGFloat = 14,
- animation: Animation? = nil,
- insets: UIEdgeInsets = .init(top: 5, left: 23.5, bottom: 53.5, right: 23.5),
- contentEdgeInsets: UIEdgeInsets = .init(top: 14, left: 0, bottom: 14, right: 0)
- ) {
- self.title = title
- self.action = action
- self.hapticFeedback = hapticFeedback
- self.backgroundColor = backgroundColor
- self.titleFont = titleFont
- self.titleColor = titleColor
- self.cornerRadius = cornerRadius
- self.animation = animation
- self.insets = insets
- self.contentEdgeInsets = contentEdgeInsets
- }
- }
-
-}
-
-// MARK: - ExpressibleByStringLiteral
-
-extension WhatsNewViewController.CompletionButton: ExpressibleByStringLiteral {
-
- /// Creates an instance initialized to the given string value.
- ///
- /// - Parameter value: The value of the new instance.
- public init(stringLiteral value: String) {
- self.init(title: value)
- }
-
-}
-
-// MARK: - Action
-
-public extension WhatsNewViewController.CompletionButton {
-
- /// The CompletionButton Action
- enum Action {
- /// Dismiss
- case dismiss
- /// Perform custom completion action
- case custom(action: (WhatsNewViewController) -> Void)
- }
-
-}
-
-// MARK: - Equatable
-
-extension WhatsNewViewController.CompletionButton.Action: Equatable {
-
- /// Returns a Boolean value indicating whether two values are equal.
- ///
- /// - Parameters:
- /// - lhs: A value to compare.
- /// - rhs: Another value to compare.
- public static func == (lhs: WhatsNewViewController.CompletionButton.Action,
- rhs: WhatsNewViewController.CompletionButton.Action) -> Bool {
- switch (lhs, rhs) {
- case (.dismiss, .dismiss):
- return true
- case (.custom, .custom):
- return true
- default:
- return false
- }
- }
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+Configuration.swift b/Sources/Configuration/WhatsNewViewController+Configuration.swift
deleted file mode 100644
index 2d485f0..0000000
--- a/Sources/Configuration/WhatsNewViewController+Configuration.swift
+++ /dev/null
@@ -1,171 +0,0 @@
-//
-// WhatsNewViewController+Configuration.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 24.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - Configuration
-
-public extension WhatsNewViewController {
-
- /// The WhatsNewViewController Configuration
- struct Configuration {
-
- // MARK: Properties
-
- /// Specifies whether the WhatsNewViewController prefers the status bar to be hidden or shown.
- public var prefersStatusBarHidden: Bool
-
- /// The background color
- public var backgroundColor: UIColor
-
- /// The TitleView
- public var titleView: TitleView
-
- /// The ItemsView
- public var itemsView: ItemsView
-
- /// The optional DetailButton
- public var detailButton: DetailButton?
-
- /// The CompletionButton
- public var completionButton: CompletionButton
-
- /// The iPad Adjustment Closure
- public var padAdjustment: PadAdjustment
-
- /// The tint color based on completionButtonTheme backgroundcolor
- public var tintColor: UIColor {
- get {
- return self.completionButton.backgroundColor
- }
- set {
- self.detailButton?.titleColor = newValue
- self.completionButton.backgroundColor = newValue
- }
- }
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - theme: The Theme. Default value `.default`
- /// - prefersStatusBarHidden: Bool value if the status bar should be be hidden or shown. Default value `.false`
- /// - backgroundColor: The background color. Default value `.whatsNewKitBackground`
- /// - titleView: The TitleView. Default value `.init()`
- /// - itemsView: The ItemsView. Default value `.init()`
- /// - detailButton: The optional DetailButton. Default value `nil`
- /// - completionButton: The completion button. Default value `.init()`
- /// - padAdjustment: The The iPad Adjustment Closure. Default value `defaultPadAdjustment`
- public init(
- theme: Theme = .default,
- prefersStatusBarHidden: Bool = false,
- backgroundColor: UIColor = .whatsNewKitBackground,
- titleView: TitleView = .init(),
- itemsView: ItemsView = .init(),
- detailButton: DetailButton? = nil,
- completionButton: CompletionButton = .init(),
- padAdjustment: @escaping PadAdjustment = Configuration.defaultPadAdjustment
- ) {
- self.prefersStatusBarHidden = prefersStatusBarHidden
- self.backgroundColor = backgroundColor
- self.titleView = titleView
- self.itemsView = itemsView
- self.detailButton = detailButton
- self.completionButton = completionButton
- self.padAdjustment = padAdjustment
- self.apply(theme: theme)
- }
-
- /// Convenience Initializer with Theme
- ///
- /// - Parameter theme: The Theme
- public init(_ theme: Theme) {
- self.init(theme: theme)
- }
-
- }
-
-}
-
-// MARK: - PadAdjustment
-
-public extension WhatsNewViewController.Configuration {
-
- /// The Pad Adjustment Closure
- typealias PadAdjustment = (inout WhatsNewViewController.Configuration) -> Void
-
- /// The default iPad Adjustment closure
- static let defaultPadAdjustment: PadAdjustment = { configuration in
- // Increase TitleView Insets
- configuration.titleView.insets.top *= 1.5
- configuration.titleView.insets.left *= 2
- configuration.titleView.insets.right *= 2
- configuration.titleView.insets.bottom *= 2
- // Increase ItemsView Insets
- configuration.itemsView.insets.top *= 2
- configuration.itemsView.insets.left *= 5
- configuration.itemsView.insets.right *= 5
- configuration.itemsView.insets.bottom *= 2
- // Increase CompletionButton Insets
- configuration.completionButton.insets.top *= 4
- configuration.completionButton.insets.left *= 5
- configuration.completionButton.insets.right *= 5
- configuration.completionButton.insets.bottom *= 2.5
- }
-
-}
-
-// MARK: - Configuration Apply Functions
-
-public extension WhatsNewViewController.Configuration {
-
- /// Apply Theme to Configuration
- ///
- /// - Parameter theme: The Theme
- /// - Returns: Discardable Configuration
- @discardableResult
- mutating func apply(
- theme: WhatsNewViewController.Theme
- ) -> WhatsNewViewController.Configuration {
- // Perform Customization
- theme.customization(&self)
- // Return self
- return self
- }
-
- /// Apply a given Animation to all Views
- ///
- /// - Parameter animation: The Animation
- /// - Returns: Discardable Configuration
- @discardableResult
- mutating func apply(
- animation: WhatsNewViewController.Animation
- ) -> WhatsNewViewController.Configuration {
- self.titleView.animation = animation
- self.itemsView.animation = animation
- self.detailButton?.animation = animation
- self.completionButton.animation = animation
- return self
- }
-
- /// Apply a given UIColor as text color to all Views
- ///
- /// - Parameter textColor: The text color
- /// - Returns: Discardable Configuration
- @discardableResult
- mutating func apply(
- textColor: UIColor
- ) -> WhatsNewViewController.Configuration {
- self.titleView.titleColor = textColor
- self.itemsView.titleColor = textColor
- self.itemsView.subtitleColor = textColor
- return self
- }
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+DetailButton.swift b/Sources/Configuration/WhatsNewViewController+DetailButton.swift
deleted file mode 100644
index 2270fb1..0000000
--- a/Sources/Configuration/WhatsNewViewController+DetailButton.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-// WhatsNewViewController+DetailButton.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 27.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - DetailButton
-
-public extension WhatsNewViewController {
-
- /// The DetailButton
- struct DetailButton: Equatable {
-
- /// The Title
- public var title: String
-
- /// The Action
- public var action: Action
-
- /// The HapticFeedback
- public var hapticFeedback: HapticFeedback?
-
- /// The title font
- public var titleFont: UIFont
-
- /// The title color
- public var titleColor: UIColor
-
- /// The Animation
- public var animation: Animation?
-
- /// The Bottom Layout Offset
- public var bottomOffset: CGFloat
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - title: The Title
- /// - action: The Action
- /// - hapticFeedback: The optional HapticFeedback
- /// - titleFont: The title font. Default value `size: 17`
- /// - titleColor: The title color. Default value `.whatsNewKitBlue`
- /// - animation: The Animation. Default value `nil`
- /// - bottomOffset: The Bottom Layout Offset. Default value `10`
- public init(
- title: String,
- action: Action,
- hapticFeedback: HapticFeedback? = nil,
- titleFont: UIFont = .systemFont(ofSize: 17),
- titleColor: UIColor = .whatsNewKitBlue,
- animation: Animation? = nil,
- bottomOffset: CGFloat = 10
- ) {
- self.title = title
- self.action = action
- self.hapticFeedback = hapticFeedback
- self.titleFont = titleFont
- self.titleColor = titleColor
- self.animation = animation
- self.bottomOffset = bottomOffset
- }
-
- }
-
-}
-
-// MARK: - Action
-
-public extension WhatsNewViewController.DetailButton {
-
- /// The DetailButton Action
- enum Action {
- /// Present Website on URL
- case website(url: String)
- /// Perform custom detail action
- case custom(action: (WhatsNewViewController) -> Void)
- }
-
-}
-
-// MARK: - Equatable
-
-extension WhatsNewViewController.DetailButton.Action: Equatable {
-
- /// Returns a Boolean value indicating whether two values are equal.
- ///
- /// - Parameters:
- /// - lhs: A value to compare.
- /// - rhs: Another value to compare.
- public static func == (
- lhs: WhatsNewViewController.DetailButton.Action,
- rhs: WhatsNewViewController.DetailButton.Action
- ) -> Bool {
- switch (lhs, rhs) {
- case (.website, .website):
- return true
- case (.custom, .custom):
- return true
- default:
- return false
- }
- }
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+HapticFeedback.swift b/Sources/Configuration/WhatsNewViewController+HapticFeedback.swift
deleted file mode 100644
index 324bad7..0000000
--- a/Sources/Configuration/WhatsNewViewController+HapticFeedback.swift
+++ /dev/null
@@ -1,123 +0,0 @@
-//
-// WhatsNewViewController+HapticFeedback.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 28.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - HapticFeedback
-
-public extension WhatsNewViewController {
-
- /// The HapticFeedback
- enum HapticFeedback: Equatable {
- /// ImpactFeedback with FeedbackStyle
- case impact(ImpactFeedbackStyle)
- /// SelectionFeedback
- case selection
- /// NotificationFeedback with FeedbackType
- case notification(NotificationFeedbackType)
- }
-
-}
-
-// MARK: - Execute
-
-extension WhatsNewViewController.HapticFeedback {
-
- /// Execute HapticFeedback
- func execute() {
- // Check if iOS 10 or greater is available
- if #available(iOS 10.0, *) {
- // Switch on self
- switch self {
- case .impact(let style):
- // UIFeedbackGenerator
- let feedbackGenerator = UIImpactFeedbackGenerator(style: style.rawStyle)
- feedbackGenerator.impactOccurred()
- case .selection:
- // UISelectionFeedbackGenerator
- let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
- selectionFeedbackGenerator.selectionChanged()
- case .notification(let type):
- // UINotificationFeedbackGenerator
- let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
- notificationFeedbackGenerator.notificationOccurred(type.rawType)
- }
- }
- }
-
-}
-
-// MARK: - ImpactFeedbackStyle
-
-public extension WhatsNewViewController.HapticFeedback {
-
- /// The ImpactFeedbackStyle
- enum ImpactFeedbackStyle: String, Codable, Equatable, Hashable, CaseIterable {
- /// Light
- case light
- /// Medium
- case medium
- /// Heavy
- case heavy
- }
-
-}
-
-// MARK: - ImpactFeedbackStyle+RawStyle
-
-extension WhatsNewViewController.HapticFeedback.ImpactFeedbackStyle {
-
- /// The Raw Style
- @available(iOS 10.0, *)
- var rawStyle: UIImpactFeedbackGenerator.FeedbackStyle {
- switch self {
- case .light:
- return .light
- case .medium:
- return .medium
- case .heavy:
- return .heavy
- }
- }
-
-}
-
-// MARK: - NotificationFeedbackType
-
-public extension WhatsNewViewController.HapticFeedback {
-
- /// The NotificationFeedbackType
- enum NotificationFeedbackType: String, Codable, Equatable, Hashable, CaseIterable {
- /// Success
- case success
- /// Warning
- case warning
- /// Error
- case error
- }
-
-}
-
-// MARK: - NotificationFeedbackType+RawType
-
-extension WhatsNewViewController.HapticFeedback.NotificationFeedbackType {
-
- /// The Raw Type
- @available(iOS 10.0, *)
- var rawType: UINotificationFeedbackGenerator.FeedbackType {
- switch self {
- case .success:
- return .success
- case .warning:
- return .warning
- case .error:
- return .error
- }
- }
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+ItemsView.swift b/Sources/Configuration/WhatsNewViewController+ItemsView.swift
deleted file mode 100644
index bfbe193..0000000
--- a/Sources/Configuration/WhatsNewViewController+ItemsView.swift
+++ /dev/null
@@ -1,142 +0,0 @@
-//
-// WhatsNewViewController+ItemsView.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 06.06.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - ItemsView
-
-public extension WhatsNewViewController {
-
- /// The ItemsViewTheme
- struct ItemsView: Equatable {
-
- /// The title font
- public var titleFont: UIFont
-
- /// The title color
- public var titleColor: UIColor
-
- /// The subtitle font
- public var subtitleFont: UIFont
-
- /// The subtitle color
- public var subtitleColor: UIColor
-
- /// The ImageSize
- public var imageSize: ImageSize
-
- /// Boolean if Image should be auto tinted
- public var autoTintImage: Bool
-
- /// The Layout
- public var layout: Layout
-
- /// The ContentMode
- public var contentMode: ContentMode
-
- /// The Animation
- public var animation: Animation?
-
- /// The Insets
- public var insets: UIEdgeInsets
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - titleFont: The title font. Default value `size: 17, weight: semibold`
- /// - titleColor: The title color. Default value `.whatsNewKitForeground`
- /// - subtitleFont: The subtitle font. Default value `size: 17`
- /// - subtitleColor: The subtitle color. Default value `.whatsNewKitForeground`
- /// - imageSize: The ImageSize. Default value `preferred`
- /// - autoTintImage: The autoTintImage boolean. Default value `true`
- /// - layout: The Layout. Default value `left`
- /// - contentMode: The ContentMode. Default value `top`
- /// - animation: The Animation. Default value `nil`
- /// - insets: The UIEdgeInsets. Default value `top: 15, left: 20, bottom: 5, right: 20`
- public init(
- titleFont: UIFont = .systemFont(ofSize: 17, weight: .semibold),
- titleColor: UIColor = .whatsNewKitForeground,
- subtitleFont: UIFont = .systemFont(ofSize: 17),
- subtitleColor: UIColor = .whatsNewKitForeground,
- imageSize: ImageSize = .preferred,
- autoTintImage: Bool = true,
- layout: Layout = .left,
- contentMode: ContentMode = .top,
- animation: Animation? = nil,
- insets: UIEdgeInsets = .init(top: 15, left: 20, bottom: 5, right: 20)
- ) {
- self.titleFont = titleFont
- self.titleColor = titleColor
- self.subtitleFont = subtitleFont
- self.subtitleColor = subtitleColor
- self.imageSize = imageSize
- self.autoTintImage = autoTintImage
- self.layout = layout
- self.contentMode = contentMode
- self.animation = animation
- self.insets = insets
- }
-
- }
-
-}
-
-// MARK: - ItemsView.Layout
-
-public extension WhatsNewViewController.ItemsView {
-
- /// The Layout
- enum Layout: String, Codable, Equatable, Hashable, CaseIterable {
- /// Left image and right aligned text
- case left
- /// Centered image and centered text
- case centered
- /// Right image and left aligned text
- case right
- }
-
-}
-
-// MARK: - ItemsView.ContentMode
-
-public extension WhatsNewViewController.ItemsView {
-
- /// The ContentMode
- enum ContentMode: String, Codable, Equatable, Hashable, CaseIterable {
- /// Top
- case top
- /// Center
- case center
- /// Fill
- case fill
- }
-
-}
-
-// MARK: - ItemsView.ImageSize
-
-public extension WhatsNewViewController.ItemsView {
-
- /// The ImageSize
- enum ImageSize: Equatable, Hashable {
- /// The original Image Size
- case original
- /// A fixed size square which contains the image, with a preserved aspect ratio
- case fixed(height: Double)
- }
-
-}
-
-// MARK: - ItemsView.ImageSize+preferred
-
-public extension WhatsNewViewController.ItemsView.ImageSize {
-
- /// The preferred Image Size `.fixed(height: 50)`
- static let preferred: WhatsNewViewController.ItemsView.ImageSize = .fixed(height: 50)
-
-}
diff --git a/Sources/Configuration/WhatsNewViewController+TitleView.swift b/Sources/Configuration/WhatsNewViewController+TitleView.swift
deleted file mode 100644
index 713c601..0000000
--- a/Sources/Configuration/WhatsNewViewController+TitleView.swift
+++ /dev/null
@@ -1,119 +0,0 @@
-//
-// WhatsNewViewController+TitleView.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 06.06.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - TitleView
-
-public extension WhatsNewViewController {
-
- /// The TitleView
- struct TitleView: Equatable {
-
- /// The TitleMode
- public var titleMode: TitleMode
-
- /// The title font
- public var titleFont: UIFont
-
- /// The title color
- public var titleColor: UIColor
-
- /// The title alignment
- public var titleAlignment: NSTextAlignment
-
- /// The Animation
- public var animation: Animation?
-
- /// The SecondaryColor
- public var secondaryColor: SecondaryColor?
-
- /// The Insets
- public var insets: UIEdgeInsets
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - titleMode: The TitleMode. Default value `.fixed`
- /// - titleFont: The title font. Default value `size: 35, weight: semibold`
- /// - titleColor: The title color. Default value `.whatsNewKitForeground`
- /// - titleAlignment: The title alignment. Default value `center`
- /// - animation: The Animation. Default value `nil`
- /// - secondaryColor: The SecondaryColor. Default value `nil`
- /// - insets: The UIEdgeInsets. Default value `top: 80, left: 20, bottom: 27, right: 20`
- public init(
- titleMode: TitleMode = .fixed,
- titleFont: UIFont = .systemFont(ofSize: 35, weight: .semibold),
- titleColor: UIColor = .whatsNewKitForeground,
- titleAlignment: NSTextAlignment = .center,
- animation: Animation? = nil,
- secondaryColor: SecondaryColor? = nil,
- insets: UIEdgeInsets = .init(top: 80, left: 20, bottom: 27, right: 20)
- ) {
- self.titleMode = titleMode
- self.titleFont = titleFont
- self.titleColor = titleColor
- self.titleAlignment = titleAlignment
- self.animation = animation
- self.secondaryColor = secondaryColor
- self.insets = insets
- }
-
- }
-
-}
-
-// MARK: - TitleMode
-
-public extension WhatsNewViewController.TitleView {
-
- /// Used to describe how the title view should be styled.
- enum TitleMode: String, Codable, Equatable, Hashable, CaseIterable {
- /// The title view should remain fixed at the top, regardless of scrolling
- case fixed
- /// The title view should scroll with the rest of the items (out of view)
- case scrolls
- }
-
-}
-
-// MARK: - SecondaryColor
-
-public extension WhatsNewViewController.TitleView {
-
- /// The SecondaryColor
- struct SecondaryColor: Equatable {
-
- /// The start index
- var startIndex: Int
-
- /// The length
- var length: Int
-
- /// The color
- var color: UIColor
-
- /// Designated Initializer
- ///
- /// - Parameters:
- /// - startIndex: The start index
- /// - length: The length
- /// - color: The color
- public init(
- startIndex: Int,
- length: Int,
- color: UIColor
- ) {
- self.startIndex = startIndex
- self.length = length
- self.color = color
- }
-
- }
-
-}
diff --git a/Sources/DataStructures/Disposable/Disposable.swift b/Sources/DataStructures/Disposable/Disposable.swift
deleted file mode 100644
index e239feb..0000000
--- a/Sources/DataStructures/Disposable/Disposable.swift
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-// Disposable.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 27.09.20.
-// Copyright © 2020 WhatsNewKit. All rights reserved.
-//
-
-import Foundation
-
-// MARK: - Disposable
-
-/// A Diposable Type
-protocol Disposable {
-
- /// Dispose
- func dispose()
-
-}
-
-// MARK: - Store In
-
-extension Disposable {
-
- /// Store the Disposable in a given DisposeBag
- /// - Parameter disposeBag: The DisposeBag where the Disposable should be stored in
- /// - Returns: The Disposable
- @discardableResult
- func store(in disposeBag: DisposeBag) -> Disposable {
- // Append Disposable to DisposeBag
- disposeBag.store(self)
- // Return Disposable
- return self
- }
-
-}
-
-// MARK: - ClosureDisposable
-
-/// A ClosureDisposable
-struct ClosureDisposable: Disposable {
-
- // MARK: Properties
-
- /// The dispose closure
- private let closure: () -> Void
-
- // MARK: Initializer
-
- /// Designated Initializer
- /// - Parameter closure: The dispose closure
- init(closure: @escaping () -> Void) {
- self.closure = closure
- }
-
- // MARK: Disposable
-
- /// Dispose
- func dispose() {
- self.closure()
- }
-
-}
diff --git a/Sources/DataStructures/Disposable/DisposeBag.swift b/Sources/DataStructures/Disposable/DisposeBag.swift
deleted file mode 100644
index c3a7a7a..0000000
--- a/Sources/DataStructures/Disposable/DisposeBag.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-// DisposeBag.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 27.09.20.
-// Copyright © 2020 WhatsNewKit. All rights reserved.
-//
-
-import Foundation
-
-// MARK: - DisposeBag
-
-/// A DisposeBag
-final class DisposeBag {
-
- // MARK: Properties
-
- /// The Disposables
- private var disposables: [Disposable]
-
- // MARK: Initializer
-
- /// Designated Initializer
- /// - Parameter disposables: The Disposables. Default vaue `.init()`
- init(disposables: [Disposable] = .init()) {
- self.disposables = disposables
- }
-
-}
-
-// MARK: - Disposable
-
-extension DisposeBag: Disposable {
-
- /// Dispose
- func dispose() {
- self.disposables.forEach { $0.dispose() }
- self.disposables.removeAll()
- }
-
-}
-
-// MARK: - Store
-
-extension DisposeBag {
-
- /// Store a given Disposable
- /// - Parameter disposable: The Disposable that should be stored
- func store(_ disposable: Disposable) {
- self.disposables.append(disposable)
- }
-
-}
diff --git a/Sources/Environment/WhatsNewEnvironment+Key.swift b/Sources/Environment/WhatsNewEnvironment+Key.swift
new file mode 100644
index 0000000..f868204
--- /dev/null
+++ b/Sources/Environment/WhatsNewEnvironment+Key.swift
@@ -0,0 +1,31 @@
+import SwiftUI
+
+// MARK: - WhatsNewEnvironment+Key
+
+public extension WhatsNewEnvironment {
+
+ /// The WhatsNewEnvironment Key
+ enum Key: EnvironmentKey {
+
+ /// The default value for the environment key
+ public static var defaultValue = WhatsNewEnvironment()
+
+ }
+
+}
+
+// MARK: - EnvironmentValues+whatsNew
+
+public extension EnvironmentValues {
+
+ /// The WhatsNewEnvironment
+ var whatsNew: WhatsNewEnvironment {
+ get {
+ self[WhatsNewEnvironment.Key.self]
+ }
+ set {
+ self[WhatsNewEnvironment.Key.self] = newValue
+ }
+ }
+
+}
diff --git a/Sources/Environment/WhatsNewEnvironment.swift b/Sources/Environment/WhatsNewEnvironment.swift
new file mode 100644
index 0000000..ec04999
--- /dev/null
+++ b/Sources/Environment/WhatsNewEnvironment.swift
@@ -0,0 +1,114 @@
+import Foundation
+
+// MARK: - WhatsNewEnvironment
+
+/// A WhatsNew Environment
+open class WhatsNewEnvironment {
+
+ // MARK: Properties
+
+ /// The current WhatsNew Version
+ public let currentVersion: WhatsNew.Version
+
+ /// The WhatsNewVersionStore
+ public let whatsNewVersionStore: WhatsNewVersionStore
+
+ /// The default WhatsNew Layout
+ public let defaultLayout: WhatsNew.Layout
+
+ /// The WhatsNewCollection
+ public let whatsNewCollection: WhatsNewCollection
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNewEnvironment`
+ /// - Parameters:
+ /// - currentVersion: The current WhatsNew Version. Default value `.current()`
+ /// - versionStore: The WhatsNewVersionStore. Default value `UserDefaultsWhatsNewVersionStore()`
+ /// - defaultLayout: The default WhatsNew Layout. Default value `.default`
+ /// - whatsNewCollection: The WhatsNewCollection
+ public init(
+ currentVersion: WhatsNew.Version = .current(),
+ versionStore: WhatsNewVersionStore = UserDefaultsWhatsNewVersionStore(),
+ defaultLayout: WhatsNew.Layout = .default,
+ whatsNewCollection: WhatsNewCollection = .init()
+ ) {
+ self.currentVersion = currentVersion
+ self.whatsNewVersionStore = versionStore
+ self.defaultLayout = defaultLayout
+ self.whatsNewCollection = whatsNewCollection
+ }
+
+ /// Creates a new instance of `WhatsNewEnvironment`
+ /// - Parameters:
+ /// - currentVersion: The current WhatsNew Version. Default value `.current()`
+ /// - versionStore: The WhatsNewVersionStore. Default value `UserDefaultsWhatsNewVersionStore()`
+ /// - defaultLayout: The default WhatsNew Layout. Default value `.default`
+ /// - whatsNewCollection: The WhatsNewCollectionProvider
+ public convenience init(
+ currentVersion: WhatsNew.Version = .current(),
+ versionStore: WhatsNewVersionStore = UserDefaultsWhatsNewVersionStore(),
+ defaultLayout: WhatsNew.Layout = .default,
+ whatsNewCollection whatsNewCollectionProvider: WhatsNewCollectionProvider
+ ) {
+ self.init(
+ currentVersion: currentVersion,
+ versionStore: versionStore,
+ defaultLayout: defaultLayout,
+ whatsNewCollection: whatsNewCollectionProvider.whatsNewCollection
+ )
+ }
+
+ /// Creates a new instance of `WhatsNewEnvironment`
+ /// - Parameters:
+ /// - currentVersion: The current WhatsNew Version. Default value `.current()`
+ /// - versionStore: The WhatsNewVersionStore. Default value `UserDefaultsWhatsNewVersionStore()`
+ /// - defaultLayout: The default WhatsNew Layout. Default value `.default`
+ /// - whatsNewCollection: A result builder closure that produces a WhatsNewCollection
+ public convenience init(
+ currentVersion: WhatsNew.Version = .current(),
+ versionStore: WhatsNewVersionStore = UserDefaultsWhatsNewVersionStore(),
+ defaultLayout: WhatsNew.Layout = .default,
+ @WhatsNewCollectionBuilder
+ whatsNewCollection: () -> WhatsNewCollection
+ ) {
+ self.init(
+ currentVersion: currentVersion,
+ versionStore: versionStore,
+ defaultLayout: defaultLayout,
+ whatsNewCollection: whatsNewCollection()
+ )
+ }
+
+ // MARK: WhatsNew
+
+ /// Retrieve a WhatsNew that should be presented to the user, if available.
+ open func whatsNew() -> WhatsNew? {
+ // Retrieve presented WhatsNew Versions from WhatsNewVersionStore
+ let presentedWhatsNewVersions = self.whatsNewVersionStore.presentedVersions
+ // Verify the current Version has not been presented
+ guard !presentedWhatsNewVersions.contains(self.currentVersion) else {
+ // Otherwise WhatsNew has already been presented for the current version
+ return nil
+ }
+ // Check if a WhatsNew is available for the current Version
+ if let whatsNew = self.whatsNewCollection.first(where: { $0.version == self.currentVersion }) {
+ // Return WhatsNew for the current Version
+ return whatsNew
+ }
+ // Otherwise initialize current minor release Version
+ let currentMinorVersion = WhatsNew.Version(
+ major: self.currentVersion.major,
+ minor: self.currentVersion.minor,
+ patch: 0
+ )
+ // Verify the current minor release Version has not been presented
+ guard !presentedWhatsNewVersions.contains(currentMinorVersion) else {
+ // Otherwise WhatsNew for current minor release Version has already been preseted
+ return nil
+ }
+ // Return WhatsNew for current minor release Version, if available
+ return self.whatsNewCollection.first { $0.version == currentMinorVersion }
+ }
+
+}
diff --git a/Sources/Extensions/Anchor.swift b/Sources/Extensions/Anchor.swift
deleted file mode 100644
index 8697f49..0000000
--- a/Sources/Extensions/Anchor.swift
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-// Anchor.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 02.02.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - Anchor
-
-/// The Anchor Protocol
-protocol Anchor {
-
- /// The Top Anchor
- var topAnchor: NSLayoutYAxisAnchor { get }
-
- /// The Bottom Anchor
- var bottomAnchor: NSLayoutYAxisAnchor { get }
-
- /// The Leading Anchor
- var leadingAnchor: NSLayoutXAxisAnchor { get }
-
- /// The Trailing Anchor
- var trailingAnchor: NSLayoutXAxisAnchor { get }
-
- /// The Width Anchor
- var widthAnchor: NSLayoutDimension { get }
-
- /// The Height Anchor
- var heightAnchor: NSLayoutDimension { get }
-
- /// The center x anchor
- var centerXAnchor: NSLayoutXAxisAnchor { get }
-
- /// The center y anchor
- var centerYAnchor: NSLayoutYAxisAnchor { get }
-
-}
-
-// MARK: - UIView+Anchor
-
-extension UIView: Anchor {}
-
-// MARK: - UILayoutGuide+Anchor
-
-extension UILayoutGuide: Anchor {}
-
-// MARK: - UIViewController+Anchor
-
-extension UIViewController {
-
- /// The Anchor
- var anchor: Anchor {
- if #available(iOS 11.0, *) {
- return self.view.safeAreaLayoutGuide
- } else {
- return self.view
- }
- }
-
-}
diff --git a/Sources/Extensions/ScrollView+alwaysBounceVertical.swift b/Sources/Extensions/ScrollView+alwaysBounceVertical.swift
new file mode 100644
index 0000000..85d1876
--- /dev/null
+++ b/Sources/Extensions/ScrollView+alwaysBounceVertical.swift
@@ -0,0 +1,113 @@
+#if os(iOS)
+import SwiftUI
+
+// MARK: - ScrollView+alwaysBounceVertical
+
+extension ScrollView {
+
+ /// Resolve the underlying `UIScrollView` to update the `alwaysBounceVertical` attribute which is
+ /// a Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content.
+ /// - Parameter alwaysBounceVertical: Bool value if the UIScrollView should always bounce vertical
+ func alwaysBounceVertical(
+ _ alwaysBounceVertical: Bool
+ ) -> some View {
+ self.overlay(
+ ViewControllerResolver { viewController in
+ // Verify UIScrollView is available
+ guard let scrollView = viewController
+ .view
+ .subviews
+ .first(where: { $0 is UIScrollView }) as? UIScrollView else {
+ // Otherwise return out of function
+ return
+ }
+ // Set alwaysBounceVertical
+ scrollView.alwaysBounceVertical = alwaysBounceVertical
+ }
+ .frame(width: 0, height: 0)
+ )
+ }
+
+}
+
+// MARK: - ViewControllerResolver
+
+/// The ViewControllerResolver
+private struct ViewControllerResolver: UIViewControllerRepresentable {
+
+ // MARK: Typealias
+
+ /// A typealias represents a UIViewController resolver closure
+ typealias Resolver = (UIViewController) -> Void
+
+ // MARK: Properties
+
+ /// The Resolver
+ let resolver: Resolver
+
+ // MARK: UIViewControllerRepresentable
+
+ /// Make ResolvedViewController
+ /// - Parameter context: The Context
+ func makeUIViewController(
+ context: Context
+ ) -> Content {
+ .init(resolver: self.resolver)
+ }
+
+ /// Update ResolvedViewController
+ /// - Parameters:
+ /// - uiViewController: The ResolvedViewController
+ /// - context: The Context
+ func updateUIViewController(
+ _ content: Content,
+ context: Context
+ ) {
+ content.resolver = self.resolver
+ }
+
+}
+
+// MARK: - ViewControllerResolver+Content
+
+private extension ViewControllerResolver {
+
+ /// The ViewControllerResolver Content
+ final class Content: UIViewController {
+
+ // MARK: Properties
+
+ /// The Resolver
+ var resolver: Resolver
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `ViewControllerResolver.Content`
+ /// - Parameter onResolve: The Resolver
+ init(
+ resolver: @escaping Resolver
+ ) {
+ self.resolver = resolver
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ /// Initializer with NSCoder is unavailable
+ @available(*, unavailable)
+ required init?(
+ coder aDecoder: NSCoder
+ ) { nil }
+
+ // MARK: View-Lifecycle
+
+ /// Did move to parent ViewController
+ /// - Parameter parent: The parent ViewController
+ override func didMove(
+ toParent parent: UIViewController?
+ ) {
+ super.didMove(toParent: parent)
+ parent.flatMap(self.resolver)
+ }
+ }
+
+}
+#endif
diff --git a/Sources/Extensions/Text+WhatsNewText.swift b/Sources/Extensions/Text+WhatsNewText.swift
new file mode 100644
index 0000000..702864d
--- /dev/null
+++ b/Sources/Extensions/Text+WhatsNewText.swift
@@ -0,0 +1,28 @@
+import SwiftUI
+
+// MARK: - Text+init(whatsNewText:)
+
+extension Text {
+
+ /// Creates a new instance of `Text` from a `WhatsNew.Text` instance
+ /// - Parameter whatsNewText: The WhatsNew Text
+ init(
+ whatsNewText: WhatsNew.Text
+ ) {
+ // Check if iOS 15 or greater is available
+ if #available(iOS 15.0, macOS 12.0, *) {
+ // Initialize with AttributedString
+ self.init(
+ AttributedString(
+ whatsNewText.attributedString
+ )
+ )
+ } else {
+ // Initialize with raw string value
+ self.init(
+ verbatim: whatsNewText.attributedString.string
+ )
+ }
+ }
+
+}
diff --git a/Sources/Extensions/UIColor+Defaults.swift b/Sources/Extensions/UIColor+Defaults.swift
deleted file mode 100644
index f58efc7..0000000
--- a/Sources/Extensions/UIColor+Defaults.swift
+++ /dev/null
@@ -1,128 +0,0 @@
-//
-// UIColor+Defaults.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 25.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - isLight
-
-extension UIColor {
-
- /// Retrieve Boolean if UIColor is light
- var isLight: Bool {
- var white: CGFloat = 0
- self.getWhite(&white, alpha: nil)
- return white > 0.5
- }
-
-}
-
-// MARK: - Template Colors
-
-public extension UIColor {
-
- /// The WhatsNewKit background color
- static var whatsNewKitBackground: UIColor {
- if #available(iOS 13.0, *) {
- return UIColor { traitCollection in
- switch traitCollection.userInterfaceStyle {
- case .light, .unspecified:
- return .whatsNewKitWhite
- case .dark:
- return .whatsNewKitDark
- @unknown default:
- return .whatsNewKitWhite
- }
- }
- } else {
- return .whatsNewKitWhite
- }
- }
-
- /// The WhatsNewKit background color
- static var whatsNewKitForeground: UIColor {
- if #available(iOS 13.0, *) {
- return UIColor { traitCollection in
- switch traitCollection.userInterfaceStyle {
- case .light, .unspecified:
- return .whatsNewKitBlack
- case .dark:
- return .whatsNewKitWhite
- @unknown default:
- return .whatsNewKitBlack
- }
- }
- } else {
- return .whatsNewKitBlack
- }
- }
-
- /// The WhatsNewKit white color
- static let whatsNewKitWhite = UIColor(
- red: 1,
- green: 1,
- blue: 1,
- alpha: 1
- )
-
- /// The WhatsNewKit black color
- static let whatsNewKitBlack = UIColor(
- red: 0,
- green: 0,
- blue: 0,
- alpha: 1
- )
-
- /// The WhatsNewKit blue color
- static let whatsNewKitBlue = UIColor(
- red: 0,
- green: 122 / 255,
- blue: 1,
- alpha: 1
- )
-
- /// The WhatsNewKit light blue color
- static let whatsNewKitLightBlue = UIColor(
- red: 95 / 255,
- green: 200 / 255,
- blue: 248 / 255,
- alpha: 1
- )
-
- /// The WhatsNewKit dark color
- static let whatsNewKitDark = UIColor(
- red: 20 / 255,
- green: 29 / 255,
- blue: 38 / 255,
- alpha: 1
- )
-
- /// The WhatsNewKit purple color
- static let whatsNewKitPurple = UIColor(
- red: 183 / 255,
- green: 35 / 255,
- blue: 1,
- alpha: 1
- )
-
- /// The WhatsNewKit red color
- static let whatsNewKitRed = UIColor(
- red: 1,
- green: 45 / 255,
- blue: 85 / 255,
- alpha: 1
- )
-
- /// The WhatsNewKit green color
- static let whatsNewKitGreen = UIColor(
- red: 76 / 255,
- green: 217 / 255,
- blue: 100 / 255,
- alpha: 1
- )
-
-}
diff --git a/Sources/Extensions/UIView+MakeConstraints.swift b/Sources/Extensions/UIView+MakeConstraints.swift
deleted file mode 100644
index b40fe88..0000000
--- a/Sources/Extensions/UIView+MakeConstraints.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// UIView+MakeConstraints.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 11.04.20.
-// Copyright © 2020 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - Make Constraints
-
-extension UIView {
-
- /// Make Constraints
- /// - Parameter constraints: The NSLayoutConstraints that should be added to the View
- @discardableResult
- func makeConstraints(_ constraints: NSLayoutConstraint...) -> Disposable {
- // Disable translates Autoresizing Mask Into Constraints
- self.translatesAutoresizingMaskIntoConstraints = false
- // Activate the given Constraints
- NSLayoutConstraint.activate(constraints)
- // Return ClosureDisposable
- return ClosureDisposable { [weak self] in
- // Deactivate all Constraints
- constraints.forEach { $0.isActive = false }
- // Remove all Constraints
- constraints.forEach { self?.removeConstraint($0) }
- }
- }
-
-}
diff --git a/Sources/Extensions/UIVisualEffectView+Representable.swift b/Sources/Extensions/UIVisualEffectView+Representable.swift
new file mode 100644
index 0000000..d15a7da
--- /dev/null
+++ b/Sources/Extensions/UIVisualEffectView+Representable.swift
@@ -0,0 +1,42 @@
+#if os(iOS)
+import SwiftUI
+
+// MARK: - UIVisualEffectView+Representable
+
+extension UIVisualEffectView {
+
+ /// A UIVisualEffect SwiftUI Representable View
+ struct Representable: UIViewRepresentable {
+
+ // MARK: Properties
+
+ /// The UIVisualEffect. Default value `UIBlurEffect(style: .regular)`
+ var effect: UIVisualEffect = UIBlurEffect(style: .regular)
+
+ // MARK: UIViewRepresentable
+
+ /// Make UIVisualEffectView
+ /// - Parameter context: The Context
+ func makeUIView(
+ context: Context
+ ) -> UIVisualEffectView {
+ .init(
+ effect: self.effect
+ )
+ }
+
+ /// Update UIVisualEffectView
+ /// - Parameters:
+ /// - visualEffectView: The UIVisualEffectView
+ /// - context: The Context
+ func updateUIView(
+ _ visualEffectView: UIVisualEffectView,
+ context: Context
+ ) {
+ visualEffectView.effect = self.effect
+ }
+
+ }
+
+}
+#endif
diff --git a/Sources/Extensions/View+WhatsNewSheet.swift b/Sources/Extensions/View+WhatsNewSheet.swift
new file mode 100644
index 0000000..0bb687f
--- /dev/null
+++ b/Sources/Extensions/View+WhatsNewSheet.swift
@@ -0,0 +1,155 @@
+import SwiftUI
+
+// MARK: - View+sheet(whatsNew:)
+
+public extension View {
+
+ /// Presents a WhatsNewView using the given WhatsNew object as a data source for the sheet’s content.
+ /// - Parameters:
+ /// - whatsNew: A Binding to an optional WhatsNew object
+ /// - versionStore: The optional WhatsNewVersionStore. Default value `nil`
+ /// - layout: The WhatsNew Layout. Default value `.default`
+ /// - onDimiss: The closure to execute when dismissing the sheet. Default value `nil`
+ func sheet(
+ whatsNew: Binding,
+ versionStore: WhatsNewVersionStore? = nil,
+ layout: WhatsNew.Layout = .default,
+ onDimiss: (() -> Void)? = nil
+ ) -> some View {
+ self.modifier(
+ ManualWhatsNewSheetViewModifier(
+ whatsNew: whatsNew,
+ versionStore: versionStore,
+ layout: layout,
+ onDismiss: onDimiss
+ )
+ )
+ }
+
+}
+
+// MARK: - ManualWhatsNewSheetViewModifier
+
+/// A Manual WhatsNew Sheet ViewModifier
+private struct ManualWhatsNewSheetViewModifier: ViewModifier {
+
+ // MARK: Properties
+
+ /// A Binding to an optional WhatsNew object
+ let whatsNew: Binding
+
+ /// The optional WhatsNewVersionStore
+ let versionStore: WhatsNewVersionStore?
+
+ /// The WhatsNew Layout
+ let layout: WhatsNew.Layout
+
+ /// The closure to execute when dismissing the sheet
+ let onDismiss: (() -> Void)?
+
+ // MARK: ViewModifier
+
+ /// Gets the current body of the caller.
+ /// - Parameter content: The Content
+ func body(
+ content: Content
+ ) -> some View {
+ // Check if a WhatsNew object is available
+ if let whatsNew = self.whatsNew.wrappedValue {
+ // Check if the WhatsNew Version has already been presented
+ if self.versionStore?.hasPresented(whatsNew.version) == true {
+ // Show content
+ content
+ } else {
+ // Show WhatsNew Sheet
+ content.sheet(
+ item: self.whatsNew,
+ onDismiss: self.onDismiss
+ ) { whatsNew in
+ WhatsNewView(
+ whatsNew: whatsNew,
+ versionStore: self.versionStore,
+ layout: self.layout
+ )
+ }
+ }
+ } else {
+ // Otherwise show content
+ content
+ }
+ }
+
+}
+
+// MARK: - View+whatsNewSheet()
+
+public extension View {
+
+ /// Auto-Presents a WhatsNewView to the user if needed based on the `WhatsNewEnvironment`
+ /// - Parameters:
+ /// - layout: The optional custom WhatsNew Layout. Default value `nil`
+ /// - onDimiss: The closure to execute when dismissing the sheet. Default value `nil`
+ func whatsNewSheet(
+ layout: WhatsNew.Layout? = nil,
+ onDismiss: (() -> Void)? = nil
+ ) -> some View {
+ self.modifier(
+ AutomaticWhatsNewSheetViewModifier(
+ layout: layout,
+ onDismiss: onDismiss
+ )
+ )
+ }
+
+}
+
+// MARK: - WhatsNewSheetViewModifier
+
+/// A Automatic WhatsNew Sheet ViewModifier
+private struct AutomaticWhatsNewSheetViewModifier: ViewModifier {
+
+ // MARK: Properties
+
+ /// The optional WhatsNew Layout
+ let layout: WhatsNew.Layout?
+
+ /// The optional closure to execute when dismissing the sheet
+ let onDismiss: (() -> Void)?
+
+ /// Bool value if sheet is dismissed
+ @State
+ private var isDismissed: Bool?
+
+ /// The WhatsNewEnvironment
+ @Environment(\.whatsNew)
+ private var whatsNewEnvironment
+
+ // MARK: ViewModifier
+
+ /// Gets the current body of the caller.
+ /// - Parameter content: The Content
+ func body(
+ content: Content
+ ) -> some View {
+ content.sheet(
+ item: .init(
+ get: {
+ self.isDismissed == true
+ ? nil
+ : self.whatsNewEnvironment.whatsNew()
+ },
+ set: {
+ self.isDismissed = $0 == nil
+ }
+ ),
+ onDismiss: self.onDismiss
+ ) { whatsNew in
+ WhatsNewView(
+ whatsNew: whatsNew,
+ versionStore: self.whatsNewEnvironment.whatsNewVersionStore,
+ layout: self.layout ?? self.whatsNewEnvironment.defaultLayout
+ )
+ }
+ }
+
+}
diff --git a/Sources/Extensions/WhatsNew+Version+Key.swift b/Sources/Extensions/WhatsNew+Version+Key.swift
new file mode 100644
index 0000000..836e511
--- /dev/null
+++ b/Sources/Extensions/WhatsNew+Version+Key.swift
@@ -0,0 +1,20 @@
+import Foundation
+
+// MARK: - WhatsNew.Version+key
+
+extension WhatsNew.Version {
+
+ /// The WhatsNew Version Key prefix
+ static let keyPrefix = "WhatsNewKit"
+
+ /// A WhatsNew Version Key the can be used to save
+ /// a WhatsNew Version to the `UserDefaults` or `NSUbiquitousKeyValueStore`
+ var key: String {
+ [
+ Self.keyPrefix,
+ self.description
+ ]
+ .joined(separator: ".")
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+Feature+Image.swift b/Sources/Models/WhatsNew+Feature+Image.swift
new file mode 100644
index 0000000..a438414
--- /dev/null
+++ b/Sources/Models/WhatsNew+Feature+Image.swift
@@ -0,0 +1,125 @@
+import SwiftUI
+
+// MARK: - WhatsNew+Item+Image
+
+public extension WhatsNew.Feature {
+
+ /// A WhatsNew Feature Image
+ struct Image {
+
+ // MARK: Properties
+
+ /// A closure that produces the Image View
+ public let view: () -> AnyView
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.Feature.Image`
+ /// - Parameters:
+ /// - image: A ViewBuilder closure that produces an Image View
+ public init(
+ @ViewBuilder
+ image: @escaping () -> Image
+ ) {
+ self.view = { .init(image()) }
+ }
+
+ }
+
+}
+
+// MARK: - Image+init(image:)
+
+public extension WhatsNew.Feature.Image {
+
+ /// Creates a new instance of `WhatsNew.Feature.Image`
+ /// - Parameter image: The Image
+ init(
+ image: Image
+ ) {
+ self.init { image }
+ }
+
+}
+
+// MARK: - Image+init(name:)
+
+public extension WhatsNew.Feature.Image {
+
+ /// Creates a new instance of `WhatsNew.Feature.Image`
+ /// - Parameters:
+ /// - name: The name of the image resource to lookup
+ /// - bundle: The bundle to search for the image resource. Default value `.main`
+ /// - renderingMode: The mode SwiftUI uses to render images. Default value `.template`
+ /// - foregroundColor: The foreground color to use when displaying this view. Default value `.accentColor`
+ init(
+ name: String,
+ bundle: Bundle = .main,
+ renderingMode: Image.TemplateRenderingMode? = .template,
+ foregroundColor: Color? = .accentColor
+ ) {
+ self.init {
+ Image(
+ name,
+ bundle: bundle
+ )
+ .renderingMode(renderingMode)
+ .font(.title)
+ .imageScale(.large)
+ .foregroundColor(foregroundColor)
+ }
+ }
+
+}
+
+// MARK: - Image+init(systemName:)
+
+public extension WhatsNew.Feature.Image {
+
+ /// Creates a new instance of `WhatsNew.Feature.Image`
+ /// - Parameters:
+ /// - systemName: The name of the system symbol image
+ /// - renderingMode: The mode SwiftUI uses to render images. Default value `.template`
+ /// - foregroundColor: The foreground color to use when displaying this view. Default value `.accentColor`
+ init(
+ systemName: String,
+ renderingMode: Image.TemplateRenderingMode? = .template,
+ foregroundColor: Color? = .accentColor
+ ) {
+ self.init {
+ Image(
+ systemName: systemName
+ )
+ .renderingMode(renderingMode)
+ .font(.title)
+ .imageScale(.large)
+ .foregroundColor(foregroundColor)
+ }
+ }
+
+ /// Creates a new instance of `WhatsNew.Feature.Image`
+ /// - Parameters:
+ /// - systemName: The name of the system symbol image
+ /// - renderingMode: The mode SwiftUI uses to render images. Default value `.template`
+ /// - symboldRenderingMode: The symbol rendering mode to use
+ /// - foregroundColor: The foreground color to use when displaying this view. Default value `.accentColor`
+ @available(iOS 15.0, macOS 12.0, *)
+ init(
+ systemName: String,
+ renderingMode: Image.TemplateRenderingMode? = .template,
+ symboldRenderingMode: SymbolRenderingMode?,
+ foregroundColor: Color? = .accentColor
+ ) {
+ self.init {
+ Image(
+ systemName: systemName
+ )
+ .renderingMode(renderingMode)
+ .symbolRenderingMode(symboldRenderingMode)
+ .font(.title)
+ .imageScale(.large)
+ .foregroundColor(foregroundColor)
+ }
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+Feature.swift b/Sources/Models/WhatsNew+Feature.swift
new file mode 100644
index 0000000..fb4a963
--- /dev/null
+++ b/Sources/Models/WhatsNew+Feature.swift
@@ -0,0 +1,73 @@
+import SwiftUI
+
+// MARK: - WhatsNew+Feature
+
+public extension WhatsNew {
+
+ /// A WhatsNew Feature
+ struct Feature {
+
+ // MARK: Properties
+
+ /// The image
+ public let image: Image
+
+ /// The title Text
+ public let title: Text
+
+ /// The subtitle Text
+ public let subtitle: Text
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.Feature`
+ /// - Parameters:
+ /// - image: The image
+ /// - title: The title Text
+ /// - subtitle: The subtitle Text
+ public init(
+ image: Image,
+ title: Text,
+ subtitle: Text
+ ) {
+ self.image = image
+ self.title = title
+ self.subtitle = subtitle
+ }
+
+ }
+
+}
+
+// MARK: - Feature+Equatable
+
+extension WhatsNew.Feature: Equatable {
+
+ /// Returns a Boolean value indicating whether two values are equal.
+ /// - Parameters:
+ /// - lhs: A value to compare.
+ /// - rhs: Another value to compare.
+ public static func == (
+ lhs: Self,
+ rhs: Self
+ ) -> Bool {
+ lhs.title == rhs.title
+ && lhs.subtitle == rhs.subtitle
+ }
+
+}
+
+// MARK: - Feature+Hashable
+
+extension WhatsNew.Feature: Hashable {
+
+ /// Hashes the essential components of this value by feeding them into the given hasher.
+ /// - Parameter hasher: The hasher to use when combining the components of this instance.
+ public func hash(
+ into hasher: inout Hasher
+ ) {
+ hasher.combine(self.title)
+ hasher.combine(self.subtitle)
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+HapticFeedback.swift b/Sources/Models/WhatsNew+HapticFeedback.swift
new file mode 100644
index 0000000..3ee87e8
--- /dev/null
+++ b/Sources/Models/WhatsNew+HapticFeedback.swift
@@ -0,0 +1,52 @@
+import Foundation
+#if os(iOS)
+import UIKit
+#endif
+
+// MARK: - WhatsNew+HapticFeedback
+
+public extension WhatsNew {
+
+ /// The WhatsNew HapticFeedback
+ enum HapticFeedback: Hashable {
+ #if os(iOS)
+ /// Impact HapticFeedback
+ case impact(
+ style: UIImpactFeedbackGenerator.FeedbackStyle? = nil,
+ intensity: CGFloat? = nil
+ )
+ /// Selection HapticFeedback
+ case selection
+ /// Notification HapticFeedback
+ case notification(
+ UINotificationFeedbackGenerator.FeedbackType = .success
+ )
+ #endif
+ }
+
+}
+
+// MARK: - Call-as-Function
+
+public extension WhatsNew.HapticFeedback {
+
+ /// Call HapticFeedback as function to execute the HapticFeedback
+ func callAsFunction() {
+ #if os(iOS)
+ switch self {
+ case .impact(let style, let intensity):
+ let feedbackGenerator = style.flatMap(UIImpactFeedbackGenerator.init) ?? .init()
+ if let intensity = intensity {
+ feedbackGenerator.impactOccurred(intensity: intensity)
+ } else {
+ feedbackGenerator.impactOccurred()
+ }
+ case .selection:
+ UISelectionFeedbackGenerator().selectionChanged()
+ case .notification(let type):
+ UINotificationFeedbackGenerator().notificationOccurred(type)
+ }
+ #endif
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+Item.swift b/Sources/Models/WhatsNew+Item.swift
deleted file mode 100644
index 0eeee39..0000000
--- a/Sources/Models/WhatsNew+Item.swift
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-// WhatsNew+Item.swift
-// WhatsNewKit
-//
-// Created by Sven Tiigi on 19.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - WhatsNew.Item
-
-public extension WhatsNew {
-
- /// The Item
- struct Item: Equatable, Hashable {
-
- // MARK: Properties
-
- /// The Title
- public let title: String
-
- /// The Subtitle
- public let subtitle: String
-
- /// The Image
- public let image: UIImage?
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - title: The Title
- /// - subtitle: The Subtitle
- /// - image: The Image
- public init(
- title: String,
- subtitle: String,
- image: UIImage?
- ) {
- self.title = title
- self.subtitle = subtitle
- self.image = image
- }
-
- }
-
-}
-
-// MARK: - Codable
-
-extension WhatsNew.Item: Codable {
-
- /// The CodingKeys
- private enum CodingKeys: CodingKey {
- /// Title
- case title
- /// Subtitle
- case subtitle
- /// Image
- case image
- }
-
- /// Initializer with Decoder
- ///
- /// - Parameter decoder: The Decoder
- /// - Throws: If decoding fails
- public init(from decoder: Decoder) throws {
- // Initialize container keyed by CodingKeys
- let container = try decoder.container(keyedBy: CodingKeys.self)
- // Decode Title
- self.title = try container.decode(String.self, forKey: .title)
- // Decode Subtitle
- self.subtitle = try container.decode(String.self, forKey: .subtitle)
- // Check if Base64 Image String is available
- if let base64 = try container.decodeIfPresent(String.self, forKey: .image) {
- // Decode Base64 to Image
- self.image = Data(base64Encoded: base64, options: .ignoreUnknownCharacters).flatMap(UIImage.init)
- } else {
- // No image base64 representation available
- self.image = nil
- }
- }
-
- /// Encode
- ///
- /// - Parameter encoder: The Encoder
- /// - Throws: If encoding fails
- public func encode(to encoder: Encoder) throws {
- // Initialize Container keyed by CodingKeys
- var container = encoder.container(keyedBy: CodingKeys.self)
- // Encode Title
- try container.encode(self.title, forKey: .title)
- // Encode Subtitle
- try container.encode(self.subtitle, forKey: .subtitle)
- // Try to Encode Image as Base64 string
- if let image = self.image, let data = image.pngData() {
- let base64 = data.base64EncodedString(options: .lineLength64Characters)
- try container.encode(base64, forKey: .image)
- }
- }
-
-}
diff --git a/Sources/Models/WhatsNew+Layout.swift b/Sources/Models/WhatsNew+Layout.swift
new file mode 100644
index 0000000..3c01e2b
--- /dev/null
+++ b/Sources/Models/WhatsNew+Layout.swift
@@ -0,0 +1,114 @@
+import SwiftUI
+
+// MARK: - WhatsNew+Layout
+
+public extension WhatsNew {
+
+ /// The WhatsNew Layout
+ struct Layout {
+
+ // MARK: Properties
+
+ /// A Boolean value if the scroll view indicator should be visible
+ public var showsScrollViewIndicators: Bool
+
+ /// The scroll view bottom content inset
+ public var scrollViewBottomContentInset: CGFloat
+
+ /// The content spacing
+ public var contentSpacing: CGFloat
+
+ /// The content padding
+ public var contentPadding: EdgeInsets
+
+ /// The feature list spacing
+ public var featureListSpacing: CGFloat
+
+ /// The feature list padding
+ public var featureListPadding: EdgeInsets
+
+ /// The feature image width
+ public var featureImageWidth: CGFloat
+
+ /// The feature horizontal spacing
+ public var featureHorizontalSpacing: CGFloat
+
+ /// The feature vertical spacing
+ public var featureVerticalSpacing: CGFloat
+
+ /// The footer action spacing
+ public var footerActionSpacing: CGFloat
+
+ /// The corner radius of the primary action button
+ public var footerPrimaryActionButtonCornerRadius: CGFloat
+
+ /// The footer visual effect view padding
+ public var footerVisualEffectViewPadding: EdgeInsets
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.Layout`
+ /// - Parameters:
+ /// - showsScrollViewIndicators: A Boolean value if the scroll view indicator should be visible. Default value `false`
+ /// - scrollViewBottomContentInset: The scroll view bottom content inset. Default value `150`
+ /// - contentSpacing: The content spacing. Default value `60`
+ /// - contentPadding: The content padding. Default value `top: 65`
+ /// - featureListSpacing: The feature list spacing. Default value `25`
+ /// - featureListPadding: The feature list padding. Default value `leading: 15`
+ /// - featureImageWidth: The feature image width. Default value `40`
+ /// - featureHorizontalSpacing: The feature horizontal spacing. Default value `15`
+ /// - featureVerticalSpacing: The feature vertical spacing. Default value `2`
+ /// - footerActionSpacing: The footer action spacing. Default value `15`
+ /// - footerPrimaryActionButtonCornerRadius: The corner radius of the primary action button. Default value `14`
+ /// - footerVisualEffectViewPadding: The footer visual effect view padding. Default value `top: -10`
+ public init(
+ showsScrollViewIndicators: Bool = false,
+ scrollViewBottomContentInset: CGFloat = 150,
+ contentSpacing: CGFloat = 60,
+ contentPadding: EdgeInsets = .init(top: 65, leading: 0, bottom: 0, trailing: 0),
+ featureListSpacing: CGFloat = 25,
+ featureListPadding: EdgeInsets = .init(top: 0, leading: 15, bottom: 0, trailing: 0),
+ featureImageWidth: CGFloat = 40,
+ featureHorizontalSpacing: CGFloat = 15,
+ featureVerticalSpacing: CGFloat = 2,
+ footerActionSpacing: CGFloat = 15,
+ footerPrimaryActionButtonCornerRadius: CGFloat = 14,
+ footerVisualEffectViewPadding: EdgeInsets = .init(top: -10, leading: 0, bottom: 0, trailing: 0)
+ ) {
+ self.showsScrollViewIndicators = showsScrollViewIndicators
+ self.scrollViewBottomContentInset = scrollViewBottomContentInset
+ self.contentSpacing = contentSpacing
+ self.contentPadding = contentPadding
+ self.featureListSpacing = featureListSpacing
+ self.featureListPadding = featureListPadding
+ self.featureImageWidth = featureImageWidth
+ self.featureHorizontalSpacing = featureHorizontalSpacing
+ self.featureVerticalSpacing = featureVerticalSpacing
+ self.footerActionSpacing = footerActionSpacing
+ self.footerPrimaryActionButtonCornerRadius = footerPrimaryActionButtonCornerRadius
+ self.footerVisualEffectViewPadding = footerVisualEffectViewPadding
+ }
+
+ }
+
+}
+
+// MARK: - Layout+default
+
+public extension WhatsNew.Layout {
+
+ /// The mutable default Layout
+ static var `default` = Self()
+
+}
+
+// MARK: - Layout+reset
+
+public extension WhatsNew.Layout {
+
+ /// Reset the Layout to default values
+ mutating func reset() {
+ self = .init()
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+PrimaryAction.swift b/Sources/Models/WhatsNew+PrimaryAction.swift
new file mode 100644
index 0000000..812ff64
--- /dev/null
+++ b/Sources/Models/WhatsNew+PrimaryAction.swift
@@ -0,0 +1,52 @@
+import SwiftUI
+
+// MARK: - WhatsNew+PrimaryAction
+
+public extension WhatsNew {
+
+ /// The WhatsNew PrimaryAction
+ struct PrimaryAction {
+
+ // MARK: Properties
+
+ /// The title Text
+ public let title: Text
+
+ /// The background color
+ public let backgroundColor: Color
+
+ /// The foreground color
+ public let foregroundColor: Color
+
+ /// The optional HapticFeedback
+ public let hapticFeedback: HapticFeedback?
+
+ /// The optional on dismiss closure
+ public let onDismiss: (() -> Void)?
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.PrimaryAction`
+ /// - Parameters:
+ /// - title: The title Text. Default value `Continue`
+ /// - backgroundColor: The background color. Default value `.accentColor`
+ /// - foregroundColor: The foreground color. Default value `.white`
+ /// - hapticFeedback: The optional HapticFeedback. Default value `nil`
+ /// - onDismiss: The optional on dismiss closure. Default value `nil`
+ public init(
+ title: Text = "Continue",
+ backgroundColor: Color = .accentColor,
+ foregroundColor: Color = .white,
+ hapticFeedback: HapticFeedback? = nil,
+ onDismiss: (() -> Void)? = nil
+ ) {
+ self.title = title
+ self.backgroundColor = backgroundColor
+ self.foregroundColor = foregroundColor
+ self.hapticFeedback = hapticFeedback
+ self.onDismiss = onDismiss
+ }
+
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+SecondaryAction+Action.swift b/Sources/Models/WhatsNew+SecondaryAction+Action.swift
new file mode 100644
index 0000000..75d0e60
--- /dev/null
+++ b/Sources/Models/WhatsNew+SecondaryAction+Action.swift
@@ -0,0 +1,92 @@
+import SwiftUI
+
+// MARK: - SecondaryAction+Action
+
+public extension WhatsNew.SecondaryAction {
+
+ /// A WhatsNew Secondary Action
+ enum Action {
+ /// Present View
+ case present(AnyView)
+ /// Custom Action
+ case custom((Binding) -> Void)
+ }
+
+}
+
+// MARK: - Action+present
+
+public extension WhatsNew.SecondaryAction.Action {
+
+ /// Present View on WhatsNewView
+ /// - Parameters:
+ /// - content: The ViewBuilder closure that produces the Content View
+ static func present(
+ @ViewBuilder
+ _ content: () -> Content
+ ) -> Self {
+ .present(.init(content()))
+ }
+
+}
+
+// MARK: - Action+dismiss
+
+public extension WhatsNew.SecondaryAction.Action {
+
+ /// Dismiss WhatsNewView
+ static let dismiss: Self = .custom { presentationMode in
+ presentationMode.wrappedValue.dismiss()
+ }
+
+}
+
+// MARK: - Action+openURL
+
+public extension WhatsNew.SecondaryAction.Action {
+
+ /// Open a URL
+ /// - Parameters:
+ /// - url: The URL that should be opened
+ /// - application: The UIApplication used to open the URL. Default value `.shared`
+ static func openURL(
+ _ url: URL?
+ ) -> Self {
+ .custom { _ in
+ // Verify URL is available
+ guard let url = url else {
+ // Otherwise return out of function
+ return
+ }
+ // Open URL
+ #if os(macOS)
+ NSWorkspace.shared.open(
+ url
+ )
+ #else
+ UIApplication.shared.open(
+ url,
+ options: .init()
+ )
+ #endif
+ }
+ }
+
+}
+
+// MARK: - Action+PresentedView
+
+extension WhatsNew.SecondaryAction.Action {
+
+ /// The WhatsNew Secondary Action PresentedView
+ struct PresentedView: Identifiable {
+
+ /// The identifier
+ var id: UUID = .init()
+
+ /// The View
+ let view: AnyView
+
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+SecondaryAction.swift b/Sources/Models/WhatsNew+SecondaryAction.swift
new file mode 100644
index 0000000..02673c9
--- /dev/null
+++ b/Sources/Models/WhatsNew+SecondaryAction.swift
@@ -0,0 +1,46 @@
+import SwiftUI
+
+// MARK: - WhatsNew+SecondaryAction
+
+public extension WhatsNew {
+
+ /// The WhatsNew SecondaryAction
+ struct SecondaryAction {
+
+ // MARK: Properties
+
+ /// The title Text
+ public let title: Text
+
+ /// The foreground color
+ public let foregroundColor: Color
+
+ /// The optional HapticFeedback
+ public let hapticFeedback: HapticFeedback?
+
+ /// The Action
+ public let action: Action
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.PrimaryAction`
+ /// - Parameters:
+ /// - title: The title Text
+ /// - foregroundColor: The foreground color. Default value `.accentColor`
+ /// - hapticFeedback: The optional HapticFeedback. Default value `nil`
+ /// - action: The Action
+ public init(
+ title: Text,
+ foregroundColor: Color = .accentColor,
+ hapticFeedback: HapticFeedback? = nil,
+ action: Action
+ ) {
+ self.title = title
+ self.foregroundColor = foregroundColor
+ self.hapticFeedback = hapticFeedback
+ self.action = action
+ }
+
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+Text.swift b/Sources/Models/WhatsNew+Text.swift
new file mode 100644
index 0000000..7d5fc54
--- /dev/null
+++ b/Sources/Models/WhatsNew+Text.swift
@@ -0,0 +1,64 @@
+import SwiftUI
+
+// MARK: - WhatsNew+Text
+
+public extension WhatsNew {
+
+ /// A WhatsNew Text
+ struct Text: Hashable {
+
+ // MARK: Properties
+
+ /// The NSAttributedString
+ public let attributedString: NSAttributedString
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.Text` from a given String
+ /// - Parameter string: The String
+ public init(
+ _ string: String
+ ) {
+ self.attributedString = .init(string: string)
+ }
+
+ }
+
+}
+
+// MARK: - AttributedString Initializer
+
+@available(iOS 15.0, macOS 12.0, *)
+public extension WhatsNew.Text {
+
+ /// Creates a new instance of `WhatsNew.Text` from a given NSAttributedString
+ /// - Parameter attributedString: The NSAttributedString
+ init(
+ _ attributedString: NSAttributedString
+ ) {
+ self.attributedString = attributedString
+ }
+
+ /// Creates a new instance of `WhatsNew.Text` from a given AttributedString
+ /// - Parameter attributedString: The AttributedString
+ init(
+ _ attributedString: AttributedString
+ ) {
+ self.attributedString = .init(attributedString)
+ }
+
+}
+
+// MARK: - ExpressibleByStringLiteral
+
+extension WhatsNew.Text: ExpressibleByStringLiteral {
+
+ /// Creates a new instance of `WhatsNew.Text` from a given String literal
+ /// - Parameter value: The String literal
+ public init(
+ stringLiteral value: String
+ ) {
+ self.init(value)
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+Title.swift b/Sources/Models/WhatsNew+Title.swift
new file mode 100644
index 0000000..60520d5
--- /dev/null
+++ b/Sources/Models/WhatsNew+Title.swift
@@ -0,0 +1,50 @@
+import SwiftUI
+
+// MARK: - WhatsNew+Title
+
+public extension WhatsNew {
+
+ /// The WhatsNew Title
+ struct Title: Hashable {
+
+ // MARK: Properties
+
+ /// The title Text
+ public let text: Text
+
+ /// The foreground color
+ public let foregroundColor: Color
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.Title`
+ /// - Parameters:
+ /// - text: The title Text
+ /// - foregroundColor: The foreground color. Default value `.primary`
+ public init(
+ text: Text,
+ foregroundColor: Color = .primary
+ ) {
+ self.text = text
+ self.foregroundColor = foregroundColor
+ }
+
+ }
+
+}
+
+// MARK: - ExpressibleByStringLiteral
+
+extension WhatsNew.Title: ExpressibleByStringLiteral {
+
+ /// Creates a new instance of `WhatsNew.Title`
+ /// - Parameter value: The String literal value
+ public init(
+ stringLiteral value: String
+ ) {
+ self.init(
+ text: .init(value)
+ )
+ }
+
+}
diff --git a/Sources/Models/WhatsNew+Version.swift b/Sources/Models/WhatsNew+Version.swift
index ce61051..6fee254 100644
--- a/Sources/Models/WhatsNew+Version.swift
+++ b/Sources/Models/WhatsNew+Version.swift
@@ -1,19 +1,13 @@
-//
-// WhatsNew+Version.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 22.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
import Foundation
-// MARK: - Version
+// MARK: - WhatsNew+Version
public extension WhatsNew {
- /// The Version
- struct Version: Codable, Equatable, Hashable {
+ /// A WhatsNew Version
+ struct Version: Hashable {
+
+ // MARK: Properties
/// The major version
public let major: Int
@@ -24,8 +18,9 @@ public extension WhatsNew {
/// The patch version
public let patch: Int
- /// Default initializer
- ///
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNew.Version`
/// - Parameters:
/// - major: The major version
/// - minor: The minor version
@@ -50,15 +45,14 @@ extension WhatsNew.Version: Comparable {
/// Returns a Boolean value indicating whether the value of the first
/// argument is less than that of the second argument.
- ///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
public static func < (
- lhs: WhatsNew.Version,
- rhs: WhatsNew.Version
+ lhs: Self,
+ rhs: Self
) -> Bool {
- return lhs.description.compare(rhs.description, options: .numeric) == .orderedAscending
+ lhs.description.compare(rhs.description, options: .numeric) == .orderedAscending
}
}
@@ -69,7 +63,13 @@ extension WhatsNew.Version: CustomStringConvertible {
/// A textual representation of this instance.
public var description: String {
- return "\(self.major).\(self.minor).\(self.patch)"
+ [
+ self.major,
+ self.minor,
+ self.patch
+ ]
+ .map(String.init)
+ .joined(separator: ".")
}
}
@@ -79,13 +79,14 @@ extension WhatsNew.Version: CustomStringConvertible {
extension WhatsNew.Version: ExpressibleByStringLiteral {
/// Creates an instance initialized to the given string value.
- ///
/// - Parameter value: The value of the new instance.
- public init(stringLiteral value: String) {
- let values = value.components(separatedBy: ".").compactMap(Int.init)
- self.major = values.indices.contains(0) ? values[0] : 0
- self.minor = values.indices.contains(1) ? values[1] : 0
- self.patch = values.indices.contains(2) ? values[2] : 0
+ public init(
+ stringLiteral value: String
+ ) {
+ let components = value.components(separatedBy: ".").compactMap(Int.init)
+ self.major = components.indices.contains(0) ? components[0] : 0
+ self.minor = components.indices.contains(1) ? components[1] : 0
+ self.patch = components.indices.contains(2) ? components[2] : 0
}
}
@@ -94,19 +95,19 @@ extension WhatsNew.Version: ExpressibleByStringLiteral {
public extension WhatsNew.Version {
- /// Retrieve WhatsNew.Version based on current Version String in Bundle
- ///
- /// - Parameter bundle: The Bundle
+ /// Retrieve current WhatsNew Version based on the current Version String in the Bundle
+ /// - Parameter bundle: The Bundle. Default value `.main`
/// - Returns: WhatsNew.Version
static func current(
- inBundle bundle: Bundle = .main
+ in bundle: Bundle = .main
) -> WhatsNew.Version {
// Retrieve Bundle short Version String
let shortVersionString = bundle.infoDictionary?["CFBundleShortVersionString"] as? String
// Return initialized Version via String Literal
- return .init(stringLiteral:
- shortVersionString ?? ""
+ return .init(
+ stringLiteral: shortVersionString ?? ""
)
}
}
+
diff --git a/Sources/Models/WhatsNew.swift b/Sources/Models/WhatsNew.swift
index 1d03426..0a8bc75 100644
--- a/Sources/Models/WhatsNew.swift
+++ b/Sources/Models/WhatsNew.swift
@@ -1,117 +1,59 @@
-//
-// WhatsNew.swift
-// WhatsNewKit
-//
-// Created by Sven Tiigi on 19.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
+import Foundation
// MARK: - WhatsNew
-/**
- The `WhatsNew` struct to declare your new app features.
- Read more on: [https://github.com/SvenTiigi/WhatsNewKit](https://github.com/SvenTiigi/WhatsNewKit)
-
- In default the `version` property will be initialized by reading out the current app version from the main bundle.
-
- # Example:
- ```
- import WhatsNewKit
-
- // WhatsNew for your current app version
- let whatsNew = WhatsNew(
- // The Title
- title: "WhatsNewKit",
- // The features you want to showcase
- items: [
- WhatsNew.Item(
- title: "Installation",
- subtitle: "You can install WhatsNewKit via CocoaPods or Carthage",
- image: UIImage(named: "installation")
- ),
- WhatsNew.Item(
- title: "Open Source",
- subtitle: "Contributions are very welcome 👨💻",
- image: UIImage(named: "openSource")
- )
- ]
- )
-
- // WhatsNew for a specific app version
- let whatsNew = WhatsNew(
- // The Title
- title: "WhatsNewKit",
- // The Version
- version: "1.0.1",
- // The features you want to showcase
- items: [...]
- )
- ```
-
- # Note:
- See the `WhatsNewViewController` to present your new app features.
- */
-public struct WhatsNew: Codable, Equatable, Hashable {
+/// A WhatsNew object
+public struct WhatsNew {
// MARK: Properties
/// The Version
public let version: Version
- /// The title
- public let title: String
+ /// The Title
+ public let title: Title
+
+ /// The Features
+ public let features: [Feature]
- /// The items
- public let items: [Item]
+ /// The PrimaryAction
+ public let primaryAction: PrimaryAction
+
+ /// The optional SecondaryAction
+ public let secondaryAction: SecondaryAction?
// MARK: Initializer
- /// Default initializer
- ///
+ /// Creates a new instance of `WhatsNew`
/// - Parameters:
- /// - version: The Version. Default value `current`
+ /// - version: The Version. Default value `.current()`
/// - title: The Title
- /// - items: The Items
+ /// - items: The Features
+ /// - primaryAction: The PrimaryAction. Default value `.init()`
+ /// - secondaryAction: The optional SecondaryAction. Default value `nil`
public init(
- version: Version = .current(inBundle: .main),
- title: String,
- items: [Item]
+ version: Version = .current(),
+ title: Title,
+ features: [Feature],
+ primaryAction: PrimaryAction = .init(),
+ secondaryAction: SecondaryAction? = nil
) {
self.version = version
self.title = title
- self.items = items
+ self.features = features
+ self.primaryAction = primaryAction
+ self.secondaryAction = secondaryAction
}
}
-// MARK: - Sequence+get
+// MARK: - Identifiable
-public extension Sequence where Element == WhatsNew {
-
- /// Retrieve WhatsNew element based on given Version
- ///
- /// - Parameter version: The Version
- /// - Returns: The first matching WhatsNew element
- func get(byVersion version: WhatsNew.Version) -> WhatsNew? {
- // First where Version is matching
- return self.first(where: {
- $0.version == version
- })
- }
+extension WhatsNew: Identifiable {
- /// Retrieve WhatsNew element based on current Version of Bundle
- ///
- /// - Parameter bundle: The Bundle
- /// - Returns: The first matching WhatsNew element
- func get(byBundle bundle: Bundle = .main) -> WhatsNew? {
- // Initialize current Version based on bundle
- let currentVersion = WhatsNew.Version.current(inBundle: bundle)
- // Return WhatsView by version
- return self.get(
- byVersion: currentVersion
- )
+ /// The stable identity of the entity associated with this instance.
+ public var id: Version {
+ self.version
}
}
diff --git a/Sources/Store/InMemoryWhatsNewVersionStore.swift b/Sources/Store/InMemoryWhatsNewVersionStore.swift
index 0c6124b..29e4ff9 100644
--- a/Sources/Store/InMemoryWhatsNewVersionStore.swift
+++ b/Sources/Store/InMemoryWhatsNewVersionStore.swift
@@ -1,11 +1,3 @@
-//
-// InMemoryWhatsNewVersionStore.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 24.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
import Foundation
// MARK: - InMemoryWhatsNewVersionStore
@@ -13,31 +5,34 @@ import Foundation
/// The InMemoryWhatsNewVersionStore
public final class InMemoryWhatsNewVersionStore {
+ // MARK: Static-Properties
+
+ /// The shared `InMemoryWhatsNewVersionStore` instance
+ public static let shared = InMemoryWhatsNewVersionStore()
+
+ // MARK: Properties
+
/// The Versions
public var versions: [WhatsNew.Version]
- /// Default initializer
+ // MARK: Initializer
+
+ /// Creates a new instance of `InMemoryWhatsNewVersionStore`
public init() {
- // Initialize Version Array
self.versions = .init()
}
- /// Clear all stored Versions
- public func clearVersions() {
- self.versions.removeAll()
- }
-
}
// MARK: - WriteableWhatsNewVersionStore
extension InMemoryWhatsNewVersionStore: WriteableWhatsNewVersionStore {
- /// Set Version
- ///
- /// - Parameter version: The Version
- public func set(version: WhatsNew.Version) {
- // Append Version
+ /// Save presented WhatsNew Version
+ /// - Parameter version: The presented WhatsNew Version that should be saved
+ public func save(
+ presentedVersion version: WhatsNew.Version
+ ) {
self.versions.append(version)
}
@@ -47,13 +42,20 @@ extension InMemoryWhatsNewVersionStore: WriteableWhatsNewVersionStore {
extension InMemoryWhatsNewVersionStore: ReadableWhatsNewVersionStore {
- /// Has Version
- ///
- /// - Parameter version: The Version
- /// - Returns: Bool if Version has been presented
- public func has(version: WhatsNew.Version) -> Bool {
- // Return if versions is contained in versions
- return self.versions.contains(version)
+ /// The WhatsNew Versions that have been already been presented
+ public var presentedVersions: [WhatsNew.Version] {
+ self.versions
+ }
+
+}
+
+// MARK: - Remove all
+
+public extension InMemoryWhatsNewVersionStore {
+
+ /// Remove all presented WhatsNew Versions
+ func removeAll() {
+ self.versions.removeAll()
}
}
diff --git a/Sources/Store/KeyValueWhatsNewVersionStore.swift b/Sources/Store/KeyValueWhatsNewVersionStore.swift
deleted file mode 100644
index 7b6b99b..0000000
--- a/Sources/Store/KeyValueWhatsNewVersionStore.swift
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-// KeyValueWhatsNewVersionStore.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 27.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import Foundation
-
-// MARK: - KeyValueable
-
-/// The KeyValueable Protocol
-public protocol KeyValueable {
-
- /// Set value for key
- ///
- /// - Parameters:
- /// - value: The value
- /// - key: The key
- func set(_ value: Any?, forKey key: String)
-
- /// Retrieve object for key
- ///
- /// - Parameter forKey: The key
- /// - Returns: The corresponding object
- func object(forKey: String) -> Any?
-
-}
-
-// MARK: - KeyValueable UserDefaults
-
-extension UserDefaults: KeyValueable {}
-
-// MARK: - NSUbiquitousKeyValueStore UserDefaults
-
-extension NSUbiquitousKeyValueStore: KeyValueable {}
-
-// MARK: - KeyValueWhatsNewVersionStore
-
-/// The KeyValueWhatsNewVersionStore
-public struct KeyValueWhatsNewVersionStore {
-
- /// The KeyValueable
- private let keyValueable: KeyValueable
-
- /// The prefix identifier
- private let prefixIdentifier: String
-
- // MARK: Initializer
-
- /// Default initializer
- ///
- /// - Parameters:
- /// - keyValueable: The KeyValueable Object. Default value `UserDefaults.standard`
- /// - prefixIdentifier: The prefix identifier. Default value `de.tiigi.whatsnewkit.`
- public init(
- keyValueable: KeyValueable = UserDefaults.standard,
- prefixIdentifier: String = "de.tiigi.whatsnewkit"
- ) {
- self.keyValueable = keyValueable
- self.prefixIdentifier = prefixIdentifier
- }
-
- // MARK: Private API
-
- /// Retrieve Key for Version
- ///
- /// - Parameter version: The Version
- /// - Returns: String key concatenated with prefix identifier
- private func key(forVersion version: WhatsNew.Version) -> String {
- return "\(self.prefixIdentifier)\(!self.prefixIdentifier.isEmpty ? "." : "")\(version)"
- }
-
-}
-
-// MARK: - WriteableWhatsNewVersionStore
-
-extension KeyValueWhatsNewVersionStore: WriteableWhatsNewVersionStore {
-
- /// Set Version
- ///
- /// - Parameter version: The Version
- public func set(version: WhatsNew.Version) {
- // Set Version String representation
- self.keyValueable.set(
- version.description,
- forKey: self.key(forVersion: version)
- )
- }
-
-}
-
-// MARK: - ReadableWhatsNewVersionStore
-
-extension KeyValueWhatsNewVersionStore: ReadableWhatsNewVersionStore {
-
- /// Has Version
- ///
- /// - Parameter version: The Version
- /// - Returns: Bool if Version has been presented
- public func has(version: WhatsNew.Version) -> Bool {
- // Retrieve object for key as String
- let versionObjectString = self.keyValueable.object(forKey: self.key(forVersion: version)) as? String
- // Return string comparison result
- return versionObjectString == version.description
- }
-
-}
diff --git a/Sources/Store/NSUbiquitousKeyValueWhatsNewVersionStore.swift b/Sources/Store/NSUbiquitousKeyValueWhatsNewVersionStore.swift
new file mode 100644
index 0000000..a0dedc3
--- /dev/null
+++ b/Sources/Store/NSUbiquitousKeyValueWhatsNewVersionStore.swift
@@ -0,0 +1,72 @@
+import Foundation
+
+/// The CloudKitWhatsNewVersionStore typealias representing a NSUbiquitousKeyValueWhatsNewVersionStore
+public typealias CloudKitWhatsNewVersionStore = NSUbiquitousKeyValueWhatsNewVersionStore
+
+// MARK: - NSUbiquitousKeyValueWhatsNewVersionStore
+
+/// A NSUbiquitousKeyValue WhatsNewVersionStore
+public struct NSUbiquitousKeyValueWhatsNewVersionStore {
+
+ // MARK: Properties
+
+ /// The NSUbiquitousKeyValueStore
+ private let ubiquitousKeyValueStore: NSUbiquitousKeyValueStore
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `NSUbiquitousKeyValueWhatsNewVersionStore`
+ /// - Parameters:
+ /// - ubiquitousKeyValueStore: The NSUbiquitousKeyValueWhatsNewVersionStore. Default value `.default`
+ public init(
+ ubiquitousKeyValueStore: NSUbiquitousKeyValueStore = .default
+ ) {
+ self.ubiquitousKeyValueStore = ubiquitousKeyValueStore
+ }
+
+}
+
+// MARK: - WriteableWhatsNewVersionStore
+
+extension NSUbiquitousKeyValueWhatsNewVersionStore: WriteableWhatsNewVersionStore {
+
+ /// Save presented WhatsNew Version
+ /// - Parameter version: The presented WhatsNew Version that should be saved
+ public func save(
+ presentedVersion version: WhatsNew.Version
+ ) {
+ self.ubiquitousKeyValueStore.set(
+ version.description,
+ forKey: version.key
+ )
+ }
+
+}
+
+// MARK: - ReadableWhatsNewVersionStore
+
+extension NSUbiquitousKeyValueWhatsNewVersionStore: ReadableWhatsNewVersionStore {
+
+ /// The WhatsNew Versions that have been already been presented
+ public var presentedVersions: [WhatsNew.Version] {
+ self.ubiquitousKeyValueStore
+ .dictionaryRepresentation
+ .filter { $0.key.starts(with: WhatsNew.Version.keyPrefix) }
+ .compactMap { $0.value as? String }
+ .map(WhatsNew.Version.init)
+ }
+
+}
+
+// MARK: - Remove all
+
+public extension NSUbiquitousKeyValueWhatsNewVersionStore {
+
+ /// Remove all presented Versions
+ func removeAll() {
+ self.presentedVersions
+ .map(\.key)
+ .forEach(self.ubiquitousKeyValueStore.removeObject)
+ }
+
+}
diff --git a/Sources/Store/UserDefaultsWhatsNewVersionStore.swift b/Sources/Store/UserDefaultsWhatsNewVersionStore.swift
new file mode 100644
index 0000000..9603cce
--- /dev/null
+++ b/Sources/Store/UserDefaultsWhatsNewVersionStore.swift
@@ -0,0 +1,69 @@
+import Foundation
+
+// MARK: - UserDefaultsWhatsNewVersionStore
+
+/// A UserDefaults WhatsNewVersionStore
+public struct UserDefaultsWhatsNewVersionStore {
+
+ // MARK: Properties
+
+ /// The UserDefaults
+ private let userDefaults: UserDefaults
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `UserDefaultsWhatsNewVersionStore`
+ /// - Parameters:
+ /// - userDefaults: The UserDefaults. Default value `.standard`
+ public init(
+ userDefaults: UserDefaults = .standard
+ ) {
+ self.userDefaults = userDefaults
+ }
+
+}
+
+// MARK: - WriteableWhatsNewVersionStore
+
+extension UserDefaultsWhatsNewVersionStore: WriteableWhatsNewVersionStore {
+
+ /// Save presented WhatsNew Version
+ /// - Parameter version: The presented WhatsNew Version that should be saved
+ public func save(
+ presentedVersion version: WhatsNew.Version
+ ) {
+ self.userDefaults.set(
+ version.description,
+ forKey: version.key
+ )
+ }
+
+}
+
+// MARK: - ReadableWhatsNewVersionStore
+
+extension UserDefaultsWhatsNewVersionStore: ReadableWhatsNewVersionStore {
+
+ /// The WhatsNew Versions that have been already been presented
+ public var presentedVersions: [WhatsNew.Version] {
+ self.userDefaults
+ .dictionaryRepresentation()
+ .filter { $0.key.starts(with: WhatsNew.Version.keyPrefix) }
+ .compactMap { $0.value as? String }
+ .map(WhatsNew.Version.init)
+ }
+
+}
+
+// MARK: - Remove all
+
+public extension UserDefaultsWhatsNewVersionStore {
+
+ /// Remove all presented Versions
+ func removeAll() {
+ self.presentedVersions
+ .map(\.key)
+ .forEach(self.userDefaults.removeObject)
+ }
+
+}
diff --git a/Sources/Store/WhatsNewVersionStore.swift b/Sources/Store/WhatsNewVersionStore.swift
index 36c4200..a322e30 100644
--- a/Sources/Store/WhatsNewVersionStore.swift
+++ b/Sources/Store/WhatsNewVersionStore.swift
@@ -1,39 +1,53 @@
-//
-// WhatsNewVersionStore.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 21.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
import Foundation
// MARK: - WhatsNewVersionStore
-/// The WhatsNewVersionStore typealias
+/// A WhatsNewVersionStore
public typealias WhatsNewVersionStore = WriteableWhatsNewVersionStore & ReadableWhatsNewVersionStore
// MARK: - WriteableWhatsNewVersionStore
-/// The WriteableWhatsNewVersionStore
+/// A Writeable WhatsNewVersionStore
public protocol WriteableWhatsNewVersionStore {
- /// Set Version
- ///
- /// - Parameter version: The Version
- func set(version: WhatsNew.Version)
+ /// Save presented WhatsNew Version
+ /// - Parameter version: The presented WhatsNew Version that should be saved
+ func save(
+ presentedVersion version: WhatsNew.Version
+ )
}
// MARK: - ReadableWhatsNewVersionStore
-/// The ReadableWhatsNewVersionStore
+/// A Readable WhatsNewVersionStore
public protocol ReadableWhatsNewVersionStore {
- /// Has Version
- ///
- /// - Parameter version: The Version
- /// - Returns: Bool if Version has been presented
- func has(version: WhatsNew.Version) -> Bool
+ /// The WhatsNew Versions that have been already been presented
+ var presentedVersions: [WhatsNew.Version] { get }
+
+}
+
+// MARK: - ReadableWhatsNewVersionStore+hasPresented
+
+public extension ReadableWhatsNewVersionStore {
+
+ /// Retrieve a bool value if a given WhatsNew Version has already been presented
+ /// - Parameter whatsNew: The WhatsNew Version to verify
+ /// - Returns: A Bool value if the given WhatsNew Version has already been preseted
+ func hasPresented(
+ _ version: WhatsNew.Version
+ ) -> Bool {
+ self.presentedVersions.contains(version)
+ }
+
+ /// Retrieve a bool value if a given WhatsNew has already been presented
+ /// - Parameter whatsNew: The WhatsNew to verify
+ /// - Returns: A Bool value if the given WhatsNew has already been preseted
+ func hasPresented(
+ _ whatsNew: WhatsNew
+ ) -> Bool {
+ self.hasPresented(whatsNew.version)
+ }
}
diff --git a/Sources/Theme/WhatsNewViewController+Theme.swift b/Sources/Theme/WhatsNewViewController+Theme.swift
deleted file mode 100644
index a2f97c9..0000000
--- a/Sources/Theme/WhatsNewViewController+Theme.swift
+++ /dev/null
@@ -1,242 +0,0 @@
-//
-// WhatsNewViewController+Theme.swift
-// WhatsNewKit-Example
-//
-// Created by Sven Tiigi on 02.11.19.
-// Copyright © 2019 WhatsNewKit. All rights reserved.
-//
-
-import UIKit
-
-// MARK: - Theme
-
-public extension WhatsNewViewController {
-
- /// The Customization typealias for an inout Configuration closure
- typealias Customization = (inout Configuration) -> Void
-
- /// The Theme
- struct Theme {
-
- /// The Customization closure
- let customization: Customization
-
- /// Default initializer
- ///
- /// - Parameter customization: The Customization
- public init(customization: @escaping Customization) {
- self.customization = customization
- }
-
- }
-
-}
-
-// MARK: - Default Template Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// The default Theme
- static var `default`: WhatsNewViewController.Theme {
- return .blue
- }
-
-}
-
-// MARK: - Blue Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// Default Theme
- static var blue: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitBackground
- configuration.apply(textColor: .whatsNewKitForeground)
- configuration.tintColor = .whatsNewKitBlue
- }
- }
-
- /// White Blue Theme (white background and blue tint color)
- static var whiteBlue: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitWhite
- configuration.apply(textColor: .whatsNewKitBlack)
- configuration.tintColor = .whatsNewKitBlue
- }
- }
-
- /// Dark Blue Theme (dark background and blue tint color)
- static var darkBlue: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitDark
- configuration.apply(textColor: .whatsNewKitWhite)
- configuration.tintColor = .whatsNewKitBlue
- }
- }
-
-}
-
-// MARK: - LightBlue Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// LightBlue Theme
- static var lightBlue: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitBackground
- configuration.apply(textColor: .whatsNewKitForeground)
- configuration.tintColor = .whatsNewKitLightBlue
- }
- }
-
- /// White LightBlue Theme (white background and light blue tint color)
- static var whiteLightBlue: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitWhite
- configuration.apply(textColor: .whatsNewKitBlack)
- configuration.tintColor = .whatsNewKitLightBlue
- }
- }
-
- /// Dark LightBlue Theme (dark background and light blue tint color)
- static var darkLightBlue: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitDark
- configuration.apply(textColor: .whatsNewKitWhite)
- configuration.tintColor = .whatsNewKitLightBlue
- }
- }
-
-}
-
-// MARK: - Orange Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// Orange Theme
- static var orange: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitBackground
- configuration.apply(textColor: .whatsNewKitForeground)
- configuration.tintColor = .orange
- }
- }
-
- /// White Orange Theme (white background and orange tint color)
- static var whiteOrange: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitWhite
- configuration.apply(textColor: .whatsNewKitBlack)
- configuration.tintColor = .orange
- }
- }
-
- /// Dark Orange Theme (dark background and orange tint color)
- static var darkOrange: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitDark
- configuration.apply(textColor: .whatsNewKitWhite)
- configuration.tintColor = .orange
- }
- }
-
-}
-
-// MARK: - Purple Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// Purple Theme
- static var purple: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitBackground
- configuration.apply(textColor: .whatsNewKitForeground)
- configuration.tintColor = .whatsNewKitPurple
- }
- }
-
- /// White Purple Theme (white background and purple tint color)
- static var whitePurple: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitWhite
- configuration.apply(textColor: .whatsNewKitBlack)
- configuration.tintColor = .whatsNewKitPurple
- }
- }
-
- /// Dark Purple Theme (dark background and purple tint color)
- static var darkPurple: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitDark
- configuration.apply(textColor: .whatsNewKitWhite)
- configuration.tintColor = .whatsNewKitPurple
- }
- }
-
-}
-
-// MARK: - Red Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// Red Theme
- static var red: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitBackground
- configuration.apply(textColor: .whatsNewKitForeground)
- configuration.tintColor = .whatsNewKitRed
- }
- }
-
- /// White Red Theme (white background and red tint color)
- static var whiteRed: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitWhite
- configuration.apply(textColor: .whatsNewKitBlack)
- configuration.tintColor = .whatsNewKitRed
- }
- }
-
- /// Dark Red Theme (dark background and red tint color)
- static var darkRed: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitDark
- configuration.apply(textColor: .whatsNewKitWhite)
- configuration.tintColor = .whatsNewKitRed
- }
- }
-
-}
-
-// MARK: - Green Theme
-
-public extension WhatsNewViewController.Theme {
-
- /// Green Theme
- static var green: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitBackground
- configuration.apply(textColor: .whatsNewKitForeground)
- configuration.tintColor = .whatsNewKitGreen
- }
- }
-
- /// White Green Theme (white background and green tint color)
- static var whiteGreen: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitWhite
- configuration.apply(textColor: .whatsNewKitBlack)
- configuration.tintColor = .whatsNewKitGreen
- }
- }
-
- /// Dark Green Theme (dark background and green tint color)
- static var darkGreen: WhatsNewViewController.Theme {
- return .init { configuration in
- configuration.backgroundColor = .whatsNewKitDark
- configuration.apply(textColor: .whatsNewKitWhite)
- configuration.tintColor = .whatsNewKitGreen
- }
- }
-
-}
diff --git a/Sources/View/WhatsNewView+FeaturesPadding.swift b/Sources/View/WhatsNewView+FeaturesPadding.swift
new file mode 100644
index 0000000..8b70ad1
--- /dev/null
+++ b/Sources/View/WhatsNewView+FeaturesPadding.swift
@@ -0,0 +1,51 @@
+import SwiftUI
+
+// MARK: - WhatsNewView+FeaturesPadding
+
+extension WhatsNewView {
+
+ /// The WhatsNewView FeaturesPadding ViewModifier
+ struct FeaturesPadding {
+
+ #if os(iOS)
+ /// The Horizontal SizeClass
+ @Environment(\.horizontalSizeClass)
+ private var horizontalSizeClass
+
+ /// The Vertical SizeClass
+ @Environment(\.verticalSizeClass)
+ private var verticalSizeClass
+ #endif
+
+ }
+
+}
+
+// MARK: - ViewModifier
+
+extension WhatsNewView.FeaturesPadding: ViewModifier {
+
+ /// Gets the current body of the caller.
+ /// - Parameter content: The Content
+ func body(
+ content: Content
+ ) -> some View {
+ #if os(macOS)
+ content.padding(.horizontal)
+ #else
+ if self.horizontalSizeClass == .regular {
+ content.padding(
+ .init(
+ top: 0,
+ leading: 100,
+ bottom: 0,
+ trailing: 100
+ )
+ )
+ } else {
+ content
+ }
+ #endif
+ }
+
+}
diff --git a/Sources/View/WhatsNewView+FooterPadding.swift b/Sources/View/WhatsNewView+FooterPadding.swift
new file mode 100644
index 0000000..8418714
--- /dev/null
+++ b/Sources/View/WhatsNewView+FooterPadding.swift
@@ -0,0 +1,67 @@
+import SwiftUI
+
+// MARK: - WhatsNewView+FooterPadding
+
+extension WhatsNewView {
+
+ /// The WhatsNewView FooterPadding ViewModifier
+ struct FooterPadding {
+
+ #if os(iOS)
+ /// The Horizontal SizeClass
+ @Environment(\.horizontalSizeClass)
+ private var horizontalSizeClass
+
+ /// The Vertical SizeClass
+ @Environment(\.verticalSizeClass)
+ private var verticalSizeClass
+ #endif
+
+ }
+
+}
+
+// MARK: - ViewModifier
+
+extension WhatsNewView.FooterPadding: ViewModifier {
+
+ /// Gets the current body of the caller.
+ /// - Parameter content: The Content
+ func body(
+ content: Content
+ ) -> some View {
+ #if os(macOS)
+ content.padding(.bottom, 30)
+ #else
+ if self.horizontalSizeClass == .regular {
+ content.padding(
+ .init(
+ top: 0,
+ leading: 150,
+ bottom: 50,
+ trailing: 150
+ )
+ )
+ } else if self.verticalSizeClass == .compact {
+ content.padding(
+ .init(
+ top: 0,
+ leading: 40,
+ bottom: 35,
+ trailing: 40
+ )
+ )
+ } else {
+ content.padding(
+ .init(
+ top: 0,
+ leading: 20,
+ bottom: 80,
+ trailing: 20
+ )
+ )
+ }
+ #endif
+ }
+
+}
diff --git a/Sources/View/WhatsNewView+PrimaryButtonStyle.swift b/Sources/View/WhatsNewView+PrimaryButtonStyle.swift
new file mode 100644
index 0000000..1b3fbd3
--- /dev/null
+++ b/Sources/View/WhatsNewView+PrimaryButtonStyle.swift
@@ -0,0 +1,53 @@
+import SwiftUI
+
+// MARK: - WhatsNewView+PrimaryButtonStyle
+
+extension WhatsNewView {
+
+ /// The WhatsNewView PrimaryButtonStyle
+ struct PrimaryButtonStyle {
+
+ /// The WhatsNew PrimaryAction
+ let primaryAction: WhatsNew.PrimaryAction
+
+ /// The WhatsNew Layout
+ let layout: WhatsNew.Layout
+
+ }
+
+}
+
+// MARK: - ButtonStyle
+
+extension WhatsNewView.PrimaryButtonStyle: ButtonStyle {
+
+ /// Creates a view that represents the body of a button.
+ /// - Parameter configuration: The properties of the button.
+ func makeBody(
+ configuration: Configuration
+ ) -> some View {
+ Group {
+ #if os(iOS)
+ HStack {
+ Spacer()
+ configuration
+ .label
+ .font(.headline.weight(.semibold))
+ .padding(.vertical)
+ Spacer()
+ }
+ #else
+ configuration
+ .label
+ .padding(.horizontal, 60)
+ .padding(.vertical, 8)
+ #endif
+ }
+ .foregroundColor(self.primaryAction.foregroundColor)
+ .background(self.primaryAction.backgroundColor)
+ .cornerRadius(self.layout.footerPrimaryActionButtonCornerRadius)
+ .opacity(configuration.isPressed ? 0.5 : 1)
+ }
+
+}
+
diff --git a/Sources/View/WhatsNewView.swift b/Sources/View/WhatsNewView.swift
new file mode 100644
index 0000000..603c7ce
--- /dev/null
+++ b/Sources/View/WhatsNewView.swift
@@ -0,0 +1,238 @@
+import SwiftUI
+
+// MARK: - WhatsNewView
+
+/// A WhatsNewView
+public struct WhatsNewView {
+
+ // MARK: Properties
+
+ /// The WhatsNew object
+ private let whatsNew: WhatsNew
+
+ /// The WhatsNewVersionStore
+ private let whatsNewVersionStore: WhatsNewVersionStore?
+
+ /// The WhatsNew Layout
+ private let layout: WhatsNew.Layout
+
+ /// The View that is presented by the SecondaryAction
+ @State
+ private var secondaryActionPresentedView: WhatsNew.SecondaryAction.Action.PresentedView?
+
+ /// The PresentationMode
+ @Environment(\.presentationMode)
+ private var presentationMode
+
+ // MARK: Initializer
+
+ /// Creates a new instance of `WhatsNewView`
+ /// - Parameters:
+ /// - whatsNew: The WhatsNew object
+ /// - versionStore: The optional WhatsNewVersionStore. Default value `nil`
+ /// - layout: The WhatsNew Layout. Default value `.default`
+ public init(
+ whatsNew: WhatsNew,
+ versionStore: WhatsNewVersionStore? = nil,
+ layout: WhatsNew.Layout = .default
+ ) {
+ self.whatsNew = whatsNew
+ self.whatsNewVersionStore = versionStore
+ self.layout = layout
+ }
+
+}
+
+// MARK: - View
+
+extension WhatsNewView: View {
+
+ /// The content and behavior of the view.
+ public var body: some View {
+ ZStack {
+ // Content ScrollView
+ ScrollView(
+ .vertical,
+ showsIndicators: self.layout.showsScrollViewIndicators
+ ) {
+ // Content Stack
+ VStack(
+ spacing: self.layout.contentSpacing
+ ) {
+ // Title
+ self.title
+ // Feature List
+ VStack(
+ alignment: .leading,
+ spacing: self.layout.featureListSpacing
+ ) {
+ // Feature
+ ForEach(
+ self.whatsNew.features,
+ id: \.self,
+ content: self.feature
+ )
+ }
+ .modifier(FeaturesPadding())
+ .padding(self.layout.featureListPadding)
+ }
+ .padding(.horizontal)
+ .padding(self.layout.contentPadding)
+ // ScrollView bottom content inset
+ Color.clear
+ .padding(
+ .bottom,
+ self.layout.scrollViewBottomContentInset
+ )
+ }
+ #if os(iOS)
+ .alwaysBounceVertical(false)
+ #endif
+ // Footer
+ VStack {
+ Spacer()
+ self.footer
+ .modifier(FooterPadding())
+ #if os(iOS)
+ .background(
+ UIVisualEffectView
+ .Representable()
+ .edgesIgnoringSafeArea(.horizontal)
+ .padding(self.layout.footerVisualEffectViewPadding)
+ )
+ #endif
+ }
+ .edgesIgnoringSafeArea(.bottom)
+ }
+ .sheet(
+ item: self.$secondaryActionPresentedView,
+ content: { $0.view }
+ )
+ .onDisappear {
+ // Save presented WhatsNew Version, if available
+ self.whatsNewVersionStore?.save(
+ presentedVersion: self.whatsNew.version
+ )
+ }
+ }
+
+}
+
+// MARK: - Title
+
+private extension WhatsNewView {
+
+ /// The Title View
+ var title: some View {
+ Text(
+ whatsNewText: self.whatsNew.title.text
+ )
+ .font(.largeTitle.bold())
+ .multilineTextAlignment(.center)
+ }
+
+}
+
+// MARK: - Feature
+
+private extension WhatsNewView {
+
+ /// The Feature View
+ /// - Parameter feature: A WhatsNew Feature
+ func feature(
+ _ feature: WhatsNew.Feature
+ ) -> some View {
+ HStack(
+ spacing: self.layout.featureHorizontalSpacing
+ ) {
+ feature
+ .image
+ .view()
+ .frame(width: self.layout.featureImageWidth)
+ VStack(
+ alignment: .leading,
+ spacing: self.layout.featureVerticalSpacing
+ ) {
+ Text(
+ whatsNewText: feature.title
+ )
+ .font(.subheadline.weight(.semibold))
+ .foregroundColor(.primary)
+ Text(
+ whatsNewText: feature.subtitle
+ )
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ }
+ .multilineTextAlignment(.leading)
+ }
+ }
+
+}
+
+// MARK: - Footer
+
+private extension WhatsNewView {
+
+ /// The Footer View
+ var footer: some View {
+ VStack(
+ spacing: self.layout.footerActionSpacing
+ ) {
+ // Check if a secondary action is available
+ if let secondaryAction = self.whatsNew.secondaryAction {
+ // Secondary Action Button
+ Button(
+ action: {
+ // Invoke HapticFeedback, if available
+ secondaryAction.hapticFeedback?()
+ // Switch on Action
+ switch secondaryAction.action {
+ case .present(let view):
+ // Set secondary action presented view
+ self.secondaryActionPresentedView = .init(view: view)
+ case .custom(let action):
+ // Invoke action with PresentationMode
+ action(self.presentationMode)
+ }
+ }
+ ) {
+ Text(
+ whatsNewText: secondaryAction.title
+ )
+ }
+ #if os(macOS)
+ .buttonStyle(
+ PlainButtonStyle()
+ )
+ #endif
+ .foregroundColor(secondaryAction.foregroundColor)
+ }
+ // Primary Action Button
+ Button(
+ action: {
+ // Invoke HapticFeedback, if available
+ self.whatsNew.primaryAction.hapticFeedback?()
+ // Dismiss
+ self.presentationMode.wrappedValue.dismiss()
+ // Invoke on dismiss, if available
+ self.whatsNew.primaryAction.onDismiss?()
+ }
+ ) {
+ Text(
+ whatsNewText: self.whatsNew.primaryAction.title
+ )
+ }
+ .buttonStyle(
+ PrimaryButtonStyle(
+ primaryAction: self.whatsNew.primaryAction,
+ layout: self.layout
+ )
+ )
+ #if os(macOS)
+ .keyboardShortcut(.defaultAction)
+ #endif
+ }
+ }
+
+}
diff --git a/Sources/ViewController/WhatsNewViewController.swift b/Sources/ViewController/WhatsNewViewController.swift
new file mode 100644
index 0000000..a2400b1
--- /dev/null
+++ b/Sources/ViewController/WhatsNewViewController.swift
@@ -0,0 +1,70 @@
+import SwiftUI
+
+// MARK: - WhatsNewHostingController
+
+#if os(macOS)
+/// A WhatsNewHostingController
+public typealias WhatsNewHostingController = NSHostingController
+#else
+/// A WhatsNewHostingController
+public typealias WhatsNewHostingController = UIHostingController
+#endif
+
+// MARK: - WhatsNewViewController
+
+/// A WhatsNew UIViewController
+open class WhatsNewViewController: WhatsNewHostingController {
+
+ /// Creates a new instance of `WhatsNewViewController`
+ /// - Parameters:
+ /// - whatsNew: The WhatsNew object
+ /// - layout: The WhatsNew Layout. Default value `.default`
+ public init(
+ whatsNew: WhatsNew,
+ layout: WhatsNew.Layout = .default
+ ) {
+ super.init(
+ rootView: .init(
+ whatsNew: whatsNew,
+ layout: layout
+ )
+ )
+ }
+
+ /// Creates a new instance of `WhatsNewViewController`
+ /// by using the provided `WhatsNewVersionStore` to verify that the
+ /// version of the WhatsNew object has not already been presented to the user.
+ /// If the version is contained in the provided `WhatsNewVersionStore` the initializer
+ /// will return `nil`
+ /// - Parameters:
+ /// - whatsNew: The WhatsNew object
+ /// - versionStore: The WhatsNewVersionStore
+ /// - layout: The WhatsNew Layout. Default value `.default`
+ public init?(
+ whatsNew: WhatsNew,
+ versionStore: WhatsNewVersionStore,
+ layout: WhatsNew.Layout = .default
+ ) {
+ // Verify WhatsNew Version has not already been presented
+ guard !versionStore.hasPresented(whatsNew) else {
+ // Otherwise return nil as WhatsNew Version
+ // has already been presented to the user
+ return nil
+ }
+ super.init(
+ rootView: .init(
+ whatsNew: whatsNew,
+ versionStore: versionStore,
+ layout: layout
+ )
+ )
+ }
+
+ /// Initializer with NSCoder is unavailable.
+ /// Please use `init(whatsNew:)` or `init?(whatsNew:versionStore:)`
+ @available(*, unavailable)
+ public required init?(
+ coder aDecoder: NSCoder
+ ) { nil }
+
+}
diff --git a/Sources/WhatsNewViewController.swift b/Sources/WhatsNewViewController.swift
deleted file mode 100644
index 172f037..0000000
--- a/Sources/WhatsNewViewController.swift
+++ /dev/null
@@ -1,521 +0,0 @@
-//
-// WhatsNewViewController.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 19.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import SafariServices
-import UIKit
-
-// MARK: - WhatsNewViewController
-
-/**
-The `WhatsNewViewController` to easily showcase your awesome new app features.
-Read more on: [https://github.com/SvenTiigi/WhatsNewKit](https://github.com/SvenTiigi/WhatsNewKit)
-
-Simply pass your `WhatsNew` struct to the `WhatsNewViewController` initializer
-and present it.
-
-```
-import WhatsNewKit
-
-// Initialize WhatsNewViewController with WhatsNew
-let whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew
-)
-
-// Present it 🤩
-self.present(whatsNewViewController, animated: true)
-```
-
-# Custom Configuration
-
- If you wish to customize the appearance of the `WhatsNewViewController` you can
- initialize it with a configured `WhatsNewViewController.Configuration`.
-
- ```
- // Initialize default Configuration
- var configuration = WhatsNewViewController.Configuration()
-
- // Customize the Configuration to your needs
- configuration.titleView.titleColor = .orange
- // ...
-
- // Initialize WhatsNewViewController with custom configuration
- let whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew,
- configuration: configuration
- )
- ```
-
- # Themes
-
- Beside the aforementioned custom configuration you can make use of predefined themes.
-
- ```
- // Initialize WhatsNewViewController with a theme
- let whatsNewViewController = WhatsNewViewController(
- whatsNew: whatsNew,
- configuration: .init(theme: .darkRed)
- )
- ```
-
- # WhatsNewVersionStore
-
- If you pass a `WhatsNewVersionStore` to the initializer will become `optional`.
- The `WhatsNewViewController` will return nil during initialization if the `version` of your passed `WhatsNew` is
- contained in the `WhatsNewVersionStore`.
-
- ```
- // Initialize WhatsNewViewController with WhatsNewVersionStore
- let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
- whatsNew: whatsNew,
- versionStore: myVersionStore
- )
-
- // Check if WhatsNewViewController is available to present it.
- if let controller = whatsNewViewController {
- // Present it as WhatsNewViewController is available
- // after init with WhatsNewVersionStore
- self.present(controller, animated: true)
- } else {
- // WhatsNewViewController is `nil`
- // this Version has already been presented
- }
- ```
-
- If the `version` is not contained in the `WhatsNewVersionStore` the `WhatsNewViewController` will
- automatically save the presented version inside the passed `WhatsNewVersionStore` to ensure that the presentation
- of your new app features only happens once.
-*/
-public final class WhatsNewViewController: UIViewController {
-
- // MARK: Properties
-
- /// The UserInterfaceIdiom
- static var userInterfaceIdiom = UIDevice.current.userInterfaceIdiom
-
- /// The preferred status bar style
- public override var preferredStatusBarStyle: UIStatusBarStyle {
- return self.view.backgroundColor?.isLight == true ? .default : .lightContent
- }
-
- /// Specifies whether the view controller prefers the status bar to be hidden or shown.
- public override var prefersStatusBarHidden: Bool {
- return self.configuration.prefersStatusBarHidden
- }
-
- /// The WhatsNew
- public let whatsNew: WhatsNew
-
- /// The Configuration
- var configuration: Configuration
-
- /// The VersionStore
- var versionStore: WhatsNewVersionStore?
-
- /// The Constraint DisposeBag
- lazy var constraintDisposeBag = DisposeBag()
-
- /// The TitleViewController
- lazy var titleViewController = WhatsNewTitleViewController(
- title: self.whatsNew.title,
- configuration: self.configuration
- )
-
- /// The ItemsViewController
- lazy var itemsViewController = WhatsNewItemsViewController(
- items: self.whatsNew.items,
- configuration: self.configuration
- )
-
- /// The ButtonViewController
- lazy var buttonViewController = WhatsNewButtonViewController(
- configuration: self.configuration,
- onPress: { [weak self] buttonType in
- // Handle Button Press
- self?.handlePress(buttonType: buttonType)
- }
- )
-
- // MARK: Initializer
-
- /// Designated Initializer with WhatsNew and Configuration
- ///
- /// - Parameters:
- /// - whatsNew: The WhatsNew
- /// - configuration: The Configuration. Default value `.init()`
- public init(
- whatsNew: WhatsNew,
- configuration: Configuration = .init()
- ) {
- // Set WhatsNew
- self.whatsNew = whatsNew
- // Set Configuration
- self.configuration = configuration
- // Super init
- super.init(nibName: nil, bundle: nil)
- }
-
- /// Convenience optional initializer with WhatsNewVersionStore.
- /// Initializer checks via WhatsNewVersionStore if Version has already been presented.
- /// If a Version has been found the initializer will return nil.
- ///
- /// - Parameters:
- /// - whatsNew: The WhatsNew
- /// - configuration: The Configuration
- /// - versionStore: The WhatsNewVersionStore
- public convenience init?(
- whatsNew: WhatsNew,
- configuration: Configuration = .init(),
- versionStore: WhatsNewVersionStore
- ) {
- // Verify VersionStore has not stored the WhatsNew Version
- guard !versionStore.has(version: whatsNew.version) else {
- // Return nil as Version has already been presented
- return nil
- }
- // Self init with WhatsNew and Configuration
- self.init(
- whatsNew: whatsNew,
- configuration: configuration
- )
- // Set VersionStore
- self.versionStore = versionStore
- }
-
- /// Convenience Initializer with WhatsNew and a Theme
- ///
- /// - Parameters:
- /// - whatsNew: The WhatsNew
- /// - theme: The Theme
- public convenience init(
- whatsNew: WhatsNew,
- theme: Theme
- ) {
- self.init(
- whatsNew: whatsNew,
- configuration: .init(theme)
- )
- }
-
- /// Convenience Initializer with WhatsNew, Theme and WhatsNewVersionStore
- /// Initializer checks via WhatsNewVersionStore if Version has already been presented.
- /// If a Version has been found the initializer will return nil.
- ///
- /// - Parameters:
- /// - whatsNew: The WhatsNew
- /// - theme: The Theme
- public convenience init?(
- whatsNew: WhatsNew,
- theme: Theme,
- versionStore: WhatsNewVersionStore
- ) {
- self.init(
- whatsNew: whatsNew,
- configuration: .init(theme),
- versionStore: versionStore
- )
- }
-
- /// Initializer with Coder always returns nil
- public required init?(coder aDecoder: NSCoder) {
- return nil
- }
-
- /// Deinit
- deinit {
- // Store WhatsNew Version
- self.storeWhatsNewVersion()
- }
-
- // MARK: View-Lifecycle
-
- /// View did load
- public override func viewDidLoad() {
- // Invoke super
- super.viewDidLoad()
- // Set background color
- self.view.backgroundColor = self.configuration.backgroundColor
- // Add Subviews
- self.addSubviews()
- // Make Constraints
- self.makeConstraints(configuration: self.configuration)
- }
-
- /// TraitCollection did change
- /// - Parameter previousTraitCollection: The previous TraitCollection
- public override func traitCollectionDidChange(
- _ previousTraitCollection: UITraitCollection?
- ) {
- // Invoke the super implementation
- super.traitCollectionDidChange(previousTraitCollection)
- // Verify InterfaceIdiom is Pad
- guard WhatsNewViewController.userInterfaceIdiom == .pad else {
- // Otherwise return out of function
- return
- }
- // Switch on horizontal size class
- switch self.traitCollection.horizontalSizeClass {
- case .compact:
- // Remake Constraints with default Configuration
- self.remakeConstraints(configuration: self.configuration)
- default:
- // Initialize mutable Configuration
- var configuration = self.configuration
- // Perform PadAdjustement on mutable Configuration
- configuration.padAdjustment(&configuration)
- // Remake Constraints with mutated Configuration
- self.remakeConstraints(
- configuration: configuration
- )
- }
- }
-
-}
-
-// MARK: - Add Subviews
-
-extension WhatsNewViewController {
-
- /// Add Subviews
- func addSubviews() {
- // Switch on TitleMode
- switch self.configuration.titleView.titleMode {
- case .fixed:
- // Add TitleViewController as Child-ViewController
- self.addChild(self.titleViewController)
- // Fixed Mode add TitleViewController view to subview
- self.view.addSubview(self.titleViewController.view)
- case .scrolls:
- // Add TitleViewController as Child-ViewController on ItemsViewController
- self.itemsViewController.addChild(self.titleViewController)
- // Scroll Mode add TitleViewController view as TableHeaderView
- self.itemsViewController.tableView.tableHeaderView = {
- // Retrieve TitleViewController View
- let titleView = self.titleViewController.view as UIView
- // Set Height
- titleView.bounds.size.height = titleView.intrinsicContentSize.height
- + self.configuration.titleView.insets.top
- + self.configuration.titleView.insets.bottom
- // Return the TitleView
- return titleView
- }()
- }
- // Invoke didMove Lifecycle on TitleViewController
- self.titleViewController.didMove(toParent: self)
- // Add ItemsViewController as Child-ViewController
- self.addChild(self.itemsViewController)
- // Add ItemsViewController View to Subview
- self.view.addSubview(self.itemsViewController.view)
- // Invoke didMove Lifecycle on ItemsViewController
- self.itemsViewController.didMove(toParent: self)
- // Add ButtonViewController as Child-ViewController
- self.addChild(self.buttonViewController)
- // Add ButtonViewController View to Subview
- self.view.addSubview(self.buttonViewController.view)
- // Invoke didMove Lifecycle on ButtonViewController
- self.buttonViewController.didMove(toParent: self)
- }
-
-}
-
-// MARK: - Make Constraints
-
-extension WhatsNewViewController {
-
- /// Remake Constraints
- /// - Parameter configuration: The Configuration that should be used
- func remakeConstraints(configuration: Configuration) {
- // Dispose the Constraint DisposeBag in order to remove any previous constraints
- self.constraintDisposeBag.dispose()
- // Re-Make Constraints with Configuration
- self.makeConstraints(configuration: configuration)
- }
-
- /// Make Constraints
- /// - Parameter configuration: The Configuration that should be used
- func makeConstraints(configuration: Configuration) {
- // Declare ItemsView TopAnchor
- let itemsViewTopAnchor: NSLayoutYAxisAnchor
- // Declare ItemsView TopAnchor Constraints
- let itemsViewTopAnchorConstant: CGFloat
- // Switch on TitleMode
- switch configuration.titleView.titleMode {
- case .fixed:
- // Make Constraints on TitleViewController
- self.titleViewController.view.makeConstraints(
- self.titleViewController.view.topAnchor.constraint(
- equalTo: self.anchor.topAnchor,
- constant: configuration.titleView.insets.top
- ),
- self.titleViewController.view.leadingAnchor.constraint(
- equalTo: self.anchor.leadingAnchor,
- constant: configuration.titleView.insets.left
- ),
- self.titleViewController.view.trailingAnchor.constraint(
- equalTo: self.anchor.trailingAnchor,
- constant: -configuration.titleView.insets.right
- )
- ).store(in: self.constraintDisposeBag)
- // Initialize ItemsView Top Anchor with BottomAnchor of the TitleViewController
- itemsViewTopAnchor = self.titleViewController.view.bottomAnchor
- // Initialize ItemsView Top Anchor Constants with ItemsView top insets and TitleView Bottom Insets
- itemsViewTopAnchorConstant = configuration.itemsView.insets.top + configuration.titleView.insets.bottom
- case .scrolls:
- // In Scrolls Mode the TitleViewController is added as the TableHeaderView of
- // the ItemsViewController so there is no need to make constraints
- // Initialize ItemsView Top Anchor with the Views TopAnchor
- itemsViewTopAnchor = self.anchor.topAnchor
- // Initialize ItemsView Top Anchor Constants with the ItemsView Top insets
- itemsViewTopAnchorConstant = configuration.itemsView.insets.top
- }
- // Make Constraints on ItemsViewController
- self.itemsViewController.view.makeConstraints(
- self.itemsViewController.view.topAnchor.constraint(
- equalTo: itemsViewTopAnchor,
- constant: itemsViewTopAnchorConstant
- ),
- self.itemsViewController.view.leadingAnchor.constraint(
- equalTo: self.anchor.leadingAnchor,
- constant: configuration.itemsView.insets.left
- ),
- self.itemsViewController.view.trailingAnchor.constraint(
- equalTo: self.anchor.trailingAnchor,
- constant: -configuration.itemsView.insets.right
- ),
- self.itemsViewController.view.bottomAnchor.constraint(
- equalTo: self.buttonViewController.view.topAnchor,
- constant: -(
- configuration.itemsView.insets.bottom
- + configuration.completionButton.insets.top
- )
- )
- ).store(in: self.constraintDisposeBag)
- // Make Constraints on ButtonViewController
- self.buttonViewController.view.makeConstraints(
- self.buttonViewController.view.leadingAnchor.constraint(
- equalTo: self.anchor.leadingAnchor,
- constant: configuration.completionButton.insets.left
- ),
- self.buttonViewController.view.trailingAnchor.constraint(
- equalTo: self.anchor.trailingAnchor,
- constant: -configuration.completionButton.insets.right
- ),
- self.buttonViewController.view.bottomAnchor.constraint(
- equalTo: self.anchor.bottomAnchor,
- constant: -configuration.completionButton.insets.bottom
- )
- ).store(in: self.constraintDisposeBag)
- }
-
-}
-
-// MARK: - Handle Button Press
-
-extension WhatsNewViewController {
-
- /// Handle Button Press
- ///
- /// - Parameter buttonType: The Button type
- func handlePress(
- buttonType: WhatsNewButtonViewController.ButtonType
- ) {
- // Switch on button type
- switch buttonType {
- case .completion:
- // Invoke HapticFeebdack for completion button
- self.configuration.completionButton.hapticFeedback?.execute()
- // Store WhatsNew Version
- self.storeWhatsNewVersion()
- // Switch on CompletionAction
- switch self.configuration.completionButton.action {
- case .dismiss:
- // Dismiss WhatsNewViewController
- self.dismiss(animated: true)
- case .custom(action: let action):
- // Perform custom action and pass self
- action(self)
- }
- case .detail:
- // Invoke HapticFeebdack for detail button
- self.configuration.detailButton?.hapticFeedback?.execute()
- // Switch on DetailAction
- switch self.configuration.detailButton?.action {
- case .some(.website(let urlString)):
- // Check if url is available
- guard let url = URL(string: urlString) else {
- // URL unavailable
- return
- }
- // Initialize SafariViewController
- let safariViewController = SFSafariViewController(url: url)
- // Check if iOS 10 or greater is available
- if #available(iOS 10.0, *) {
- // Set tint color
- safariViewController.preferredControlTintColor = self.configuration.tintColor
- // Set Bar tint Color
- safariViewController.preferredBarTintColor = self.configuration.backgroundColor
- }
- // Present ViewController
- self.present(safariViewController, animated: true)
- case .some(.custom(action: let action)):
- // Perform custom action and pass self
- action(self)
- case .none:
- break
- }
- }
- }
-
-}
-
-// MARK: - Store Version
-
-extension WhatsNewViewController {
-
- /// Store presented WhatsNew Version
- func storeWhatsNewVersion() {
- // Store Version if VersionStore is available
- self.versionStore?.set(version: self.whatsNew.version)
- // Clear VersionStore
- self.versionStore = nil
- }
-
-}
-
-// MARK: Present/Push
-
-public extension WhatsNewViewController {
-
- /// Present WhatsNewViewController
- ///
- /// - Parameters:
- /// - viewController: The ViewController to present on
- /// - animated: If present should be animated. Default value `true`
- /// - completion: The completion closure. Default value `nil`
- func present(
- on viewController: UIViewController?,
- animated: Bool = true,
- completion: (() -> Void)? = nil
- ) {
- // Present WhatsNewViewController
- viewController?.present(self, animated: animated, completion: completion)
- }
-
- /// Push WhatsNewViewController
- ///
- /// - Parameters:
- /// - navigationController: The NavigationController
- /// - animated: Should be pushed animated. Default value `true`
- func push(
- on navigationController: UINavigationController?,
- animated: Bool = true
- ) {
- // Push WhatsNewViewController
- navigationController?.pushViewController(self, animated: animated)
- }
-
-}
diff --git a/Tests/BaseTests.swift b/Tests/BaseTests.swift
deleted file mode 100644
index 878fd8c..0000000
--- a/Tests/BaseTests.swift
+++ /dev/null
@@ -1,111 +0,0 @@
-//
-// BaseTests.swift
-// TveeeKit
-//
-// Created by Sven Tiigi on 09.04.18.
-// Copyright © 2018 opwoco GmbH. All rights reserved.
-//
-
-import XCTest
-@testable import WhatsNewKit
-
-class BaseTests: XCTestCase {
-
- /// The timeout value while waiting
- /// that an expectation is fulfilled
- lazy var expectationTimeout: TimeInterval = 10.0
-
- /// Random String
- lazy var randomString: String = self.generateRandomString()
-
- /// Random Data
- lazy var randomData: Data = self.generateRandomData()
-
- /// Random Int
- lazy var randomInt: Int = self.generateRandomInt()
-
- /// Random Double
- lazy var randomDouble: Double = self.generateRandomDouble()
-
- /// Random WhatsNew
- lazy var randomWhatsNew: WhatsNew = self.generateRandomWhatsNew()
-
- /// System Image
- lazy var image: UIImage? = UIButton(type: .infoLight).image(for: .normal)
-
- /// SetUp
- override func setUp() {
- super.setUp()
- // Disable continueAfterFailure
- self.continueAfterFailure = false
- }
-
- /// Perform test with expectation
- ///
- /// - Parameters:
- /// - name: The expectation name
- /// - timeout: The optional custom timeout
- /// - execution: The test execution
- /// - completionHandler: The optional XCWaitCompletionHandler
- func performTest(name: String = "\(#function): Line \(#line)",
- timeout: TimeInterval? = nil,
- execution: (XCTestExpectation) -> Void,
- completionHandler: XCWaitCompletionHandler? = nil) {
- // Create expectation with function name
- let expectation = self.expectation(description: name)
- // Perform test execution with expectation
- execution(expectation)
- // Wait for expectation been fulfilled with custom or default timeout
- self.waitForExpectations(
- timeout: timeout.flatMap { $0 } ?? self.expectationTimeout,
- handler: completionHandler
- )
- }
-
- /// Generate a random String
- ///
- /// - Returns: A random string
- func generateRandomString() -> String {
- return UUID().uuidString
- }
-
- /// Generate a random Int
- ///
- /// - Returns: A random Int
- func generateRandomInt() -> Int {
- return Int(arc4random_uniform(100))
- }
-
- /// Generate a random Double
- ///
- /// - Returns: A random Double
- func generateRandomDouble() -> Double {
- return Double(self.generateRandomInt())
- }
-
- /// Generate a random Data object
- ///
- /// - Returns: Random Data object
- func generateRandomData() -> Data {
- return Data(self.generateRandomString().utf8)
- }
-
- /// Generate a random WhatsNew
- func generateRandomWhatsNew() -> WhatsNew {
- return WhatsNew(
- version: .init(
- major: self.generateRandomInt(),
- minor: self.generateRandomInt(),
- patch: self.generateRandomInt()),
- title: self.generateRandomString(),
- items: [
- WhatsNew.Item(
- title: self.generateRandomString(),
- subtitle: self.generateRandomString(),
- image: nil
- )
- ]
- )
- }
-
-}
diff --git a/Tests/Models/WhatsNewItemTests.swift b/Tests/Models/WhatsNewItemTests.swift
deleted file mode 100644
index 416411e..0000000
--- a/Tests/Models/WhatsNewItemTests.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// WhatsNewItemTests.swift
-// WhatsNewKit-iOS Tests
-//
-// Created by Sven Tiigi on 01.06.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import XCTest
-@testable import WhatsNewKit
-
-class WhatsNewItemTests: BaseTests {
-
- func testCodableWithImage() {
- let whatsNewItem = WhatsNew.Item(
- title: self.generateRandomString(),
- subtitle: self.generateRandomString(),
- image: self.image
- )
- let data: Data
- do {
- data = try JSONEncoder().encode(whatsNewItem)
- } catch {
- XCTFail(error.localizedDescription)
- return
- }
- let whatsNewItemDecoded: WhatsNew.Item
- do {
- whatsNewItemDecoded = try JSONDecoder().decode(WhatsNew.Item.self, from: data)
- } catch {
- XCTFail(error.localizedDescription)
- return
- }
- XCTAssertEqual(whatsNewItem.title, whatsNewItemDecoded.title)
- XCTAssertEqual(whatsNewItem.subtitle, whatsNewItemDecoded.subtitle)
- XCTAssertNotNil(whatsNewItemDecoded.image)
- }
-
- func testCodableNoImage() {
- let whatsNewItem = WhatsNew.Item(
- title: self.generateRandomString(),
- subtitle: self.generateRandomString(),
- image: nil
- )
- let data: Data
- do {
- data = try JSONEncoder().encode(whatsNewItem)
- } catch {
- XCTFail(error.localizedDescription)
- return
- }
- let whatsNewItemDecoded: WhatsNew.Item
- do {
- whatsNewItemDecoded = try JSONDecoder().decode(WhatsNew.Item.self, from: data)
- } catch {
- XCTFail(error.localizedDescription)
- return
- }
- XCTAssertEqual(whatsNewItem.title, whatsNewItemDecoded.title)
- XCTAssertEqual(whatsNewItem.subtitle, whatsNewItemDecoded.subtitle)
- XCTAssertNil(whatsNewItem.image)
- }
-
-}
diff --git a/Tests/Models/WhatsNewTests.swift b/Tests/Models/WhatsNewTests.swift
deleted file mode 100644
index 6de27bc..0000000
--- a/Tests/Models/WhatsNewTests.swift
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// WhatsNewTests.swift
-// WhatsNewKit-iOS
-//
-// Created by Sven Tiigi on 24.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import XCTest
-@testable import WhatsNewKit
-
-class WhatsNewTests: BaseTests {
-
- func testGetByVersion() {
- let whatsNew1 = self.generateRandomWhatsNew()
- let whatsNew2 = self.generateRandomWhatsNew()
- let targetVersion = whatsNew1.version
- let whatsNews = [whatsNew1, whatsNew2]
- XCTAssertEqual(whatsNew1, whatsNews.get(byVersion: targetVersion))
- }
-
- func testGetByBundle() {
- let whatsNew1 = self.generateRandomWhatsNew()
- let whatsNew2 = self.generateRandomWhatsNew()
- class FakeBundle: Bundle {
- var version: WhatsNew.Version?
- override var infoDictionary: [String : Any]? {
- guard let version = self.version?.description else {
- return nil
- }
- return ["CFBundleShortVersionString": version]
- }
- }
- let fakeBundle = FakeBundle()
- fakeBundle.version = whatsNew1.version
- let whatsNews = [whatsNew1, whatsNew2]
- XCTAssertEqual(whatsNew1, whatsNews.get(byBundle: fakeBundle))
- }
-
-}
diff --git a/Tests/Models/WhatsNewVersionTests.swift b/Tests/Models/WhatsNewVersionTests.swift
deleted file mode 100644
index 722c832..0000000
--- a/Tests/Models/WhatsNewVersionTests.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// WhatsNewVersionTests.swift
-// WhatsNewKit-iOS Tests
-//
-// Created by Sven Tiigi on 24.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import XCTest
-@testable import WhatsNewKit
-
-class WhatsNewVersionTests: BaseTests {
-
- func testStringLiteral() {
- let major = self.generateRandomInt()
- let minor = self.generateRandomInt()
- let patch = self.generateRandomInt()
- let versionString = "\(major).\(minor).\(patch)"
- let version = WhatsNew.Version(stringLiteral: versionString)
- XCTAssertEqual(major, version.major)
- XCTAssertEqual(minor, version.minor)
- XCTAssertEqual(patch, version.patch)
- XCTAssertEqual(versionString, version.description)
- }
-
- func testInvalidStringLiteral() {
- let version = WhatsNew.Version(stringLiteral: "")
- XCTAssertEqual(0, version.major)
- XCTAssertEqual(0, version.minor)
- XCTAssertEqual(0, version.patch)
- }
-
- func testCurrentInBundle() {
- class FakeBundle: Bundle {
- override var infoDictionary: [String : Any]? {
- return ["CFBundleShortVersionString": self.version]
- }
- var version: String = ""
- }
- let major = self.generateRandomInt()
- let minor = self.generateRandomInt()
- let patch = self.generateRandomInt()
- let versionString = "\(major).\(minor).\(patch)"
- let fakeBundle = FakeBundle()
- fakeBundle.version = versionString
- let version = WhatsNew.Version.current(inBundle: fakeBundle)
- XCTAssertEqual(major, version.major)
- XCTAssertEqual(minor, version.minor)
- XCTAssertEqual(patch, version.patch)
- XCTAssertEqual(versionString, version.description)
- }
-
- func testCurrentInBundleUnavailable() {
- class FakeBundle: Bundle {
- override var infoDictionary: [String : Any]? {
- return nil
- }
- }
- let fakeBundle = FakeBundle()
- let version = WhatsNew.Version.current(inBundle: fakeBundle)
- XCTAssertEqual(0, version.major)
- XCTAssertEqual(0, version.minor)
- XCTAssertEqual(0, version.patch)
- XCTAssertEqual("0.0.0", version.description)
- }
-
- func testComparable() {
- let version0 = WhatsNew.Version(major: 0, minor: 0, patch: 1)
- let version1 = WhatsNew.Version(major: 1, minor: 0, patch: 0)
- XCTAssert(version0 < version1)
- XCTAssert(version1 > version0)
- }
-
- func testLargeMinorVersionComparable() {
- let version0 = WhatsNew.Version(major: 3, minor: 17, patch: 7)
- let version1 = WhatsNew.Version(major: 4, minor: 7, patch: 7)
- XCTAssertTrue(version0 < version1)
- XCTAssertTrue(version1 > version0)
- XCTAssertFalse(version1 == version0)
- }
-
- func testEquality() {
- let version0 = WhatsNew.Version(major: 1, minor: 0, patch: 0)
- let version1 = WhatsNew.Version(major: 1, minor: 0, patch: 0)
- XCTAssertFalse(version0 < version1)
- XCTAssertFalse(version1 > version0)
- XCTAssertTrue(version1 == version0)
- }
-
-}
diff --git a/Tests/Stores/InMemoryWhatsNewVersionStoreTests.swift b/Tests/Stores/InMemoryWhatsNewVersionStoreTests.swift
deleted file mode 100644
index 8c9f4a7..0000000
--- a/Tests/Stores/InMemoryWhatsNewVersionStoreTests.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// InMemoryWhatsNewVersionStoreTests.swift
-// WhatsNewKit-iOS Tests
-//
-// Created by Sven Tiigi on 01.06.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import XCTest
-@testable import WhatsNewKit
-
-class InMemoryWhatsNewVersionStoreTests: BaseTests {
-
- func testInMemory() {
- let randomWhatsNewVersion = self.randomWhatsNew.version
- let inMemoryWhatsNewVersionStore = InMemoryWhatsNewVersionStore()
- XCTAssertFalse(inMemoryWhatsNewVersionStore.has(version: randomWhatsNewVersion))
- inMemoryWhatsNewVersionStore.set(version: randomWhatsNewVersion)
- XCTAssert(inMemoryWhatsNewVersionStore.has(version: randomWhatsNewVersion))
- inMemoryWhatsNewVersionStore.clearVersions()
- XCTAssertFalse(inMemoryWhatsNewVersionStore.has(version: randomWhatsNewVersion))
- }
-
-}
diff --git a/Tests/Stores/KeyValueWhatsNewVersionStoreTests.swift b/Tests/Stores/KeyValueWhatsNewVersionStoreTests.swift
deleted file mode 100644
index 4f57914..0000000
--- a/Tests/Stores/KeyValueWhatsNewVersionStoreTests.swift
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// KeyValueWhatsNewVersionStoreTests.swift
-// WhatsNewKit-iOS Tests
-//
-// Created by Sven Tiigi on 01.06.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
-import XCTest
-@testable import WhatsNewKit
-
-class KeyValueWhatsNewVersionStoreTests: BaseTests {
-
- func testKeyValueStore() {
- class FakeKeyValueable: KeyValueable {
- var objects: [String: Any] = [:]
- func set(_ value: Any?, forKey key: String) {
- self.objects[key] = value
- }
- func object(forKey: String) -> Any? {
- return self.objects[forKey]
- }
- }
- let fakeKeyValueable = FakeKeyValueable()
- let keyValueWhatsNewVersionStore = KeyValueWhatsNewVersionStore(keyValueable: fakeKeyValueable, prefixIdentifier: "unit.test")
- let randomWhatsNewVersion = self.randomWhatsNew.version
- keyValueWhatsNewVersionStore.set(version: randomWhatsNewVersion)
- XCTAssertEqual(randomWhatsNewVersion.description, fakeKeyValueable.objects["unit.test.\(randomWhatsNewVersion.description)"] as? String)
- XCTAssert(keyValueWhatsNewVersionStore.has(version: randomWhatsNewVersion))
- XCTAssertFalse(keyValueWhatsNewVersionStore.has(version: self.generateRandomWhatsNew().version))
- }
-
- func testKeyValueWhatsNewVersionNSUbiquitousKeyValueStore() {
- let iCloudKeyValue = NSUbiquitousKeyValueStore.default
- let version = self.randomWhatsNew.version
- let versionKey = "unit.test.\(version.description)"
- iCloudKeyValue.removeObject(forKey: version.description)
- XCTAssertNil(iCloudKeyValue.object(forKey: versionKey))
- let keyValueWhatsNewVersionStore = KeyValueWhatsNewVersionStore(keyValueable: iCloudKeyValue, prefixIdentifier: "unit.test")
- XCTAssertFalse(keyValueWhatsNewVersionStore.has(version: version))
- keyValueWhatsNewVersionStore.set(version: version)
- XCTAssertTrue(keyValueWhatsNewVersionStore.has(version: version))
- XCTAssertEqual(iCloudKeyValue.object(forKey: versionKey) as? String, version.description)
- iCloudKeyValue.removeObject(forKey: version.description)
- }
-
- func testKeyValueWhatsNewVersionUserDefaultsStore() {
- let userDefaults = UserDefaults.standard
- let version = self.randomWhatsNew.version
- let versionKey = "unit.test.\(version.description)"
- userDefaults.removeObject(forKey: versionKey)
- XCTAssertNil(userDefaults.object(forKey: versionKey))
- let keyValueWhatsNewVersionStore = KeyValueWhatsNewVersionStore(keyValueable: userDefaults, prefixIdentifier: "unit.test")
- XCTAssertFalse(keyValueWhatsNewVersionStore.has(version: version))
- keyValueWhatsNewVersionStore.set(version: version)
- XCTAssertTrue(keyValueWhatsNewVersionStore.has(version: version))
- XCTAssertEqual(userDefaults.object(forKey: versionKey) as? String, version.description)
- userDefaults.removeObject(forKey: versionKey)
- }
-
-}
-
diff --git a/Tests/WhatsNewEnvironmentTests.swift b/Tests/WhatsNewEnvironmentTests.swift
new file mode 100644
index 0000000..4cd09e8
--- /dev/null
+++ b/Tests/WhatsNewEnvironmentTests.swift
@@ -0,0 +1,103 @@
+import XCTest
+@testable import WhatsNewKit
+
+// MARK: - WhatsNewEnvironmentTests
+
+/// The WhatsNewEnvironmentTests
+final class WhatsNewEnvironmentTests: WhatsNewKitTestCase {
+
+ func testInitial() {
+ let version_1_0_0: WhatsNew.Version = "1.0.0"
+ let whatsNew_1_0_0 = self.makeWhatsNew(from: version_1_0_0)
+ let versionStore = InMemoryWhatsNewVersionStore()
+ let environment = WhatsNewEnvironment(
+ currentVersion: version_1_0_0,
+ versionStore: versionStore,
+ whatsNewCollection: [whatsNew_1_0_0]
+ )
+ XCTAssertEqual(
+ version_1_0_0,
+ environment.whatsNew()?.version
+ )
+ }
+
+ func testInitialReopen() {
+ let version_1_0_0: WhatsNew.Version = "1.0.0"
+ let whatsNew_1_0_0 = self.makeWhatsNew(from: version_1_0_0)
+ let versionStore = InMemoryWhatsNewVersionStore()
+ versionStore.save(presentedVersion: version_1_0_0)
+ let environment = WhatsNewEnvironment(
+ currentVersion: version_1_0_0,
+ versionStore: versionStore,
+ whatsNewCollection: [whatsNew_1_0_0]
+ )
+ XCTAssertNil(
+ environment.whatsNew()
+ )
+ }
+
+ func testUpdate() {
+ let version_1_0_0: WhatsNew.Version = "1.0.0"
+ let whatsNew_1_0_0 = self.makeWhatsNew(from: version_1_0_0)
+ let version_1_0_1: WhatsNew.Version = "1.0.1"
+ let whatsNew_1_0_1 = self.makeWhatsNew(from: version_1_0_1)
+ let versionStore = InMemoryWhatsNewVersionStore()
+ versionStore.save(presentedVersion: version_1_0_0)
+ let environment = WhatsNewEnvironment(
+ currentVersion: version_1_0_1,
+ versionStore: versionStore,
+ whatsNewCollection: [
+ whatsNew_1_0_0,
+ whatsNew_1_0_1
+ ]
+ .shuffled()
+ )
+ XCTAssertEqual(
+ version_1_0_1,
+ environment.whatsNew()?.version
+ )
+ }
+
+ func testInitialAfterUpdates() {
+ let version_1_0_0: WhatsNew.Version = "1.0.0"
+ let whatsNew_1_0_0 = self.makeWhatsNew(from: version_1_0_0)
+ let version_1_1_0: WhatsNew.Version = "1.1.0"
+ let whatsNew_1_1_0 = self.makeWhatsNew(from: version_1_1_0)
+ let versionStore = InMemoryWhatsNewVersionStore()
+ let environment = WhatsNewEnvironment(
+ currentVersion: "1.1.1",
+ versionStore: versionStore,
+ whatsNewCollection: [
+ whatsNew_1_0_0,
+ whatsNew_1_1_0
+ ]
+ .shuffled()
+ )
+ XCTAssertEqual(
+ version_1_1_0,
+ environment.whatsNew()?.version
+ )
+ }
+
+ func testInitialAfterUpdatesReopen() {
+ let version_1_0_0: WhatsNew.Version = "1.0.0"
+ let whatsNew_1_0_0 = self.makeWhatsNew(from: version_1_0_0)
+ let version_1_1_0: WhatsNew.Version = "1.1.0"
+ let whatsNew_1_1_0 = self.makeWhatsNew(from: version_1_1_0)
+ let versionStore = InMemoryWhatsNewVersionStore()
+ versionStore.save(presentedVersion: version_1_1_0)
+ let environment = WhatsNewEnvironment(
+ currentVersion: "1.1.1",
+ versionStore: versionStore,
+ whatsNewCollection: [
+ whatsNew_1_0_0,
+ whatsNew_1_1_0
+ ]
+ .shuffled()
+ )
+ XCTAssertNil(
+ environment.whatsNew()
+ )
+ }
+
+}
diff --git a/Tests/WhatsNewKitTestCase.swift b/Tests/WhatsNewKitTestCase.swift
new file mode 100644
index 0000000..544154e
--- /dev/null
+++ b/Tests/WhatsNewKitTestCase.swift
@@ -0,0 +1,42 @@
+import XCTest
+@testable import WhatsNewKit
+
+// MARK: - WhatsNewKitTestCase
+
+/// The WhatsNewKitTestCase
+class WhatsNewKitTestCase: XCTestCase {
+
+ // MARK: XCTestCase-Lifecycle
+
+ /// Provides an opportunity to reset state before calling each test method in a test case.
+ override func setUp() {
+ super.setUp()
+ // Disable continueAfterFailure
+ self.continueAfterFailure = false
+ }
+
+ // MARK: Convenience Functions
+
+ /// Make a random WhatsNew Version
+ func makeRandomWhatsNewVersion() -> WhatsNew.Version {
+ let randomVersionRange = 0...9
+ return .init(
+ major: .random(in: randomVersionRange),
+ minor: .random(in: randomVersionRange),
+ patch: .random(in: randomVersionRange)
+ )
+ }
+
+ /// Create a WhatsNew instance from a given WhatsNew Version
+ /// - Parameter version: The WhatsNew Version
+ func makeWhatsNew(
+ from version: WhatsNew.Version
+ ) -> WhatsNew {
+ .init(
+ version: version,
+ title: "",
+ features: .init()
+ )
+ }
+
+}
diff --git a/Tests/WhatsNewVersionStoreTests.swift b/Tests/WhatsNewVersionStoreTests.swift
new file mode 100644
index 0000000..42e0d96
--- /dev/null
+++ b/Tests/WhatsNewVersionStoreTests.swift
@@ -0,0 +1,111 @@
+import XCTest
+@testable import WhatsNewKit
+
+// MARK: - WhatsNewVersionStoreTests
+
+/// The WhatsNewVersionStoreTests
+final class WhatsNewVersionStoreTests: WhatsNewKitTestCase {
+
+ func testInMemoryWhatsNewVersionStore() {
+ let inMemoryWhatsNewVersionStore = InMemoryWhatsNewVersionStore()
+ let version = self.executeVersionStoreTest(inMemoryWhatsNewVersionStore)
+ XCTAssertEqual(
+ [version],
+ inMemoryWhatsNewVersionStore.versions
+ )
+ inMemoryWhatsNewVersionStore.removeAll()
+ XCTAssert(
+ inMemoryWhatsNewVersionStore.presentedVersions.isEmpty
+ )
+ XCTAssert(
+ inMemoryWhatsNewVersionStore.versions.isEmpty
+ )
+ }
+
+ func testUserDefaultsWhatsNewVersionStore() {
+ final class FakeUserDefaults: UserDefaults {
+ var store: [String: Any] = .init()
+
+ override func set(_ value: Any?, forKey defaultName: String) {
+ self.store[defaultName] = value
+ }
+
+ override func dictionaryRepresentation() -> [String: Any] {
+ self.store
+ }
+ }
+ let fakeUserDefaults = FakeUserDefaults()
+ let userDefaultsWhatsNewVersionStore = UserDefaultsWhatsNewVersionStore(
+ userDefaults: fakeUserDefaults
+ )
+ let version = self.executeVersionStoreTest(userDefaultsWhatsNewVersionStore)
+ XCTAssertEqual(
+ fakeUserDefaults.store.count,
+ 1
+ )
+ XCTAssertEqual(
+ version,
+ (fakeUserDefaults.store[version.key] as? String).flatMap(WhatsNew.Version.init)
+ )
+ userDefaultsWhatsNewVersionStore.removeAll()
+ XCTAssert(
+ userDefaultsWhatsNewVersionStore.presentedVersions.isEmpty
+ )
+ XCTAssert(
+ fakeUserDefaults.store.isEmpty
+ )
+ }
+
+ func testNSUbiquitousKeyValueWhatsNewVersionStore() {
+ final class FakeNSUbiquitousKeyValueStore: NSUbiquitousKeyValueStore {
+ var store: [String: Any] = .init()
+
+ override var dictionaryRepresentation: [String: Any] {
+ self.store
+ }
+
+ override func set(_ value: Any?, forKey defaultName: String) {
+ self.store[defaultName] = value
+ }
+ }
+ let fakeNSUbiquitousKeyValueStore = FakeNSUbiquitousKeyValueStore()
+ let ubiquitousKeyValueWhatsNewVersionStore = NSUbiquitousKeyValueWhatsNewVersionStore(
+ ubiquitousKeyValueStore: fakeNSUbiquitousKeyValueStore
+ )
+ let version = self.executeVersionStoreTest(ubiquitousKeyValueWhatsNewVersionStore)
+ XCTAssertEqual(
+ fakeNSUbiquitousKeyValueStore.store.count,
+ 1
+ )
+ XCTAssertEqual(
+ version,
+ (fakeNSUbiquitousKeyValueStore.store[version.key] as? String).flatMap(WhatsNew.Version.init)
+ )
+ ubiquitousKeyValueWhatsNewVersionStore.removeAll()
+ XCTAssert(
+ ubiquitousKeyValueWhatsNewVersionStore.presentedVersions.isEmpty
+ )
+ XCTAssert(
+ fakeNSUbiquitousKeyValueStore.store.isEmpty
+ )
+ }
+
+}
+
+private extension WhatsNewVersionStoreTests {
+
+ func executeVersionStoreTest(
+ _ versionStore: WhatsNewVersionStore
+ ) -> WhatsNew.Version {
+ let version = self.makeRandomWhatsNewVersion()
+ XCTAssert(versionStore.presentedVersions.isEmpty)
+ XCTAssertFalse(versionStore.hasPresented(version))
+ versionStore.save(
+ presentedVersion: version
+ )
+ XCTAssertEqual([version], versionStore.presentedVersions)
+ XCTAssert(versionStore.hasPresented(version))
+ return version
+ }
+
+}
diff --git a/Tests/WhatsNewVersionTests.swift b/Tests/WhatsNewVersionTests.swift
new file mode 100644
index 0000000..7ef42aa
--- /dev/null
+++ b/Tests/WhatsNewVersionTests.swift
@@ -0,0 +1,85 @@
+import XCTest
+@testable import WhatsNewKit
+
+// MARK: - WhatsNewVersionTests
+
+/// The WhatsNewVersionTests
+final class WhatsNewVersionTests: WhatsNewKitTestCase {
+
+ func testStringLiteral() {
+ let whatsNewVersionString = "9.9.9"
+ let whatsNewVersion = WhatsNew.Version(stringLiteral: whatsNewVersionString)
+ XCTAssertEqual(
+ whatsNewVersionString,
+ whatsNewVersion.description
+ )
+ }
+
+ func testBadStringLiteral() {
+ let whatsNewVersionString = UUID().uuidString
+ let whatsNewVersion = WhatsNew.Version(stringLiteral: whatsNewVersionString)
+ XCTAssertEqual(
+ "0.0.0",
+ whatsNewVersion.description
+ )
+ }
+
+ func testComparable() {
+ let sortedVersions: [WhatsNew.Version] = [
+ "1.0.0",
+ "1.0.1",
+ "1.1.1",
+ "1.1.2",
+ "1.2.0",
+ "2.0.0",
+ "2.0.1",
+ "2.1.0"
+ ]
+ XCTAssertEqual(
+ sortedVersions,
+ sortedVersions.shuffled().sorted(by: <)
+ )
+ }
+
+ func testCurrent() {
+ class FakeBundle: Bundle {
+ let shortVersionString: String
+ init(shortVersionString: String) {
+ self.shortVersionString = shortVersionString
+ super.init()
+ }
+ override var infoDictionary: [String : Any]? {
+ [
+ "CFBundleShortVersionString": self.shortVersionString
+ ]
+ }
+ }
+ let version = self.makeRandomWhatsNewVersion()
+ let fakeBundle = FakeBundle(shortVersionString: version.description)
+ XCTAssertEqual(
+ version,
+ WhatsNew.Version.current(in: fakeBundle)
+ )
+ let fakeBundleEmptyVersion = FakeBundle(shortVersionString: "")
+ XCTAssertEqual(
+ WhatsNew.Version(major: 0, minor: 0, patch: 0),
+ WhatsNew.Version.current(in: fakeBundleEmptyVersion)
+ )
+ }
+
+ func testKeyPrefix() {
+ XCTAssertEqual(
+ "WhatsNewKit",
+ WhatsNew.Version.keyPrefix
+ )
+ }
+
+ func testKey() {
+ let version = self.makeRandomWhatsNewVersion()
+ XCTAssertEqual(
+ "WhatsNewKit.\(version.description)",
+ version.key
+ )
+ }
+
+}
diff --git a/Tests/WhatsNewViewControllerTests.swift b/Tests/WhatsNewViewControllerTests.swift
index e5d3465..99933c1 100644
--- a/Tests/WhatsNewViewControllerTests.swift
+++ b/Tests/WhatsNewViewControllerTests.swift
@@ -1,124 +1,27 @@
-//
-// WhatsNewViewControllerTests.swift
-// WhatsNewKit-iOS Tests
-//
-// Created by Sven Tiigi on 26.05.18.
-// Copyright © 2018 WhatsNewKit. All rights reserved.
-//
-
import XCTest
@testable import WhatsNewKit
-class WhatsNewViewControllerTests: BaseTests {
-
- func testAvailableVersionInVersionStore() {
- let versionStore = InMemoryWhatsNewVersionStore()
- versionStore.set(version: self.randomWhatsNew.version)
- XCTAssertNil(WhatsNewViewController(whatsNew: self.randomWhatsNew, versionStore: versionStore))
- }
+// MARK: - WhatsNewViewControllerTests
+
+/// The WhatsNewViewControllerTests
+final class WhatsNewViewControllerTests: WhatsNewKitTestCase {
- func testUnavailableVersionInVersionStore() {
+ func testInitializer() {
let versionStore = InMemoryWhatsNewVersionStore()
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew, versionStore: versionStore)
- XCTAssertNotNil(whatsNewViewController)
- whatsNewViewController?.handlePress(buttonType: .completion)
- XCTAssert(versionStore.has(version: self.randomWhatsNew.version))
- }
-
- func testWhatsNewPassedToControllerEquatable() {
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew)
- XCTAssertEqual(self.randomWhatsNew, whatsNewViewController.whatsNew)
- }
-
- func testCoderInitializer() {
- XCTAssertNil(WhatsNewViewController(coder: .init()))
- }
-
- func testBackgroundColor() {
- let color = UIColor.purple
- var configuration = WhatsNewViewController.Configuration()
- configuration.backgroundColor = color
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew, configuration: configuration)
- XCTAssertEqual(color, whatsNewViewController.view.backgroundColor)
- }
-
- func testPreferredStatusBarStyleLightContent() {
- let color = UIColor.black
- var configuration = WhatsNewViewController.Configuration()
- configuration.backgroundColor = color
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew, configuration: configuration)
- XCTAssertEqual(.lightContent, whatsNewViewController.preferredStatusBarStyle)
- }
-
- func testPreferredStatusBarStyleDefault() {
- let color = UIColor.white
- var configuration = WhatsNewViewController.Configuration()
- configuration.backgroundColor = color
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew, configuration: configuration)
- XCTAssertEqual(.default, whatsNewViewController.preferredStatusBarStyle)
- }
-
- func testPadAdjustment() {
- WhatsNewViewController.userInterfaceIdiom = .pad
- self.performTest(execution: { expectation in
- var configuration = WhatsNewViewController.Configuration()
- configuration.padAdjustment = { _ in
- expectation.fulfill()
- }
- let controller = WhatsNewViewController(whatsNew: self.randomWhatsNew, configuration: configuration)
- controller.traitCollectionDidChange(nil)
- }, completionHandler: { _ in
- WhatsNewViewController.userInterfaceIdiom = UIDevice.current.userInterfaceIdiom
- })
- }
-
- func testNoPadAdjustment() {
- WhatsNewViewController.userInterfaceIdiom = .phone
- var configuration = WhatsNewViewController.Configuration()
- configuration.padAdjustment = { _ in
- XCTFail("Pad Adjustment mustn't be called when UserInterfaceIdiom is Phone")
- }
- _ = WhatsNewViewController(whatsNew: self.randomWhatsNew, configuration: configuration)
- WhatsNewViewController.userInterfaceIdiom = UIDevice.current.userInterfaceIdiom
- }
-
- func testPresent() {
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew)
- class FakeViewController: UIViewController {
- var viewControllerToPresent: UIViewController?
- var shouldAnimate: Bool?
- override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
- self.viewControllerToPresent = viewControllerToPresent
- self.shouldAnimate = flag
- completion?()
- }
- }
- let fakeViewViewController = FakeViewController()
- let animated = true
- self.performTest(execution: { expectation in
- whatsNewViewController.present(on: fakeViewViewController, animated: animated, completion: {
- XCTAssert(fakeViewViewController.viewControllerToPresent === whatsNewViewController)
- XCTAssertEqual(fakeViewViewController.shouldAnimate, true)
- expectation.fulfill()
- })
- })
- }
-
- func testPush() {
- let whatsNewViewController = WhatsNewViewController(whatsNew: self.randomWhatsNew)
- class FakeNavigationController: UINavigationController {
- var pushedViewController: UIViewController?
- var shouldAnimate: Bool?
- override func pushViewController(_ viewController: UIViewController, animated: Bool) {
- self.pushedViewController = viewController
- self.shouldAnimate = animated
- }
- }
- let fakeNavigationController = FakeNavigationController()
- let animated = true
- whatsNewViewController.push(on: fakeNavigationController, animated: animated)
- XCTAssert(fakeNavigationController.pushedViewController === whatsNewViewController)
- XCTAssertEqual(fakeNavigationController.shouldAnimate, animated)
+ let whatsNew = self.makeWhatsNew(from: self.makeRandomWhatsNewVersion())
+ XCTAssertNotNil(
+ WhatsNewViewController(
+ whatsNew: whatsNew,
+ versionStore: versionStore
+ )
+ )
+ versionStore.save(presentedVersion: whatsNew.version)
+ XCTAssertNil(
+ WhatsNewViewController(
+ whatsNew: whatsNew,
+ versionStore: versionStore
+ )
+ )
}
}
diff --git a/WhatsNewKit.podspec b/WhatsNewKit.podspec
deleted file mode 100644
index 25afdcc..0000000
--- a/WhatsNewKit.podspec
+++ /dev/null
@@ -1,14 +0,0 @@
-Pod::Spec.new do |s|
- s.name = "WhatsNewKit"
- s.version = "1.3.7"
- s.summary = "Showcase your awesome new app features"
- s.homepage = "https://github.com/SvenTiigi/WhatsNewKit"
- s.social_media_url = 'http://twitter.com/SvenTiigi'
- s.license = 'MIT'
- s.author = { "Sven Tiigi" => "sven.tiigi@gmail.com" }
- s.source = { :git => "https://github.com/SvenTiigi/WhatsNewKit.git", :tag => s.version.to_s }
- s.swift_version = "5.0"
- s.ios.deployment_target = "9.0"
- s.source_files = 'Sources/**/*'
- s.frameworks = 'Foundation', 'UIKit'
-end
diff --git a/WhatsNewKit.xcodeproj/project.pbxproj b/WhatsNewKit.xcodeproj/project.pbxproj
deleted file mode 100644
index 9437a38..0000000
--- a/WhatsNewKit.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,1027 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 47;
- objects = {
-
-/* Begin PBXBuildFile section */
- 2264FE192441B2630030A4CE /* TitleModeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2264FE182441B2630030A4CE /* TitleModeConfiguration.swift */; };
- 3D04214620C2D68C00DA5126 /* BaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04214520C2D68C00DA5126 /* BaseTests.swift */; };
- 3D04215120C2D6A100DA5126 /* WhatsNewViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04214720C2D6A000DA5126 /* WhatsNewViewControllerTests.swift */; };
- 3D04215320C2D6A100DA5126 /* WhatsNewVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04214B20C2D6A100DA5126 /* WhatsNewVersionTests.swift */; };
- 3D04215420C2D6A100DA5126 /* WhatsNewItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04214C20C2D6A100DA5126 /* WhatsNewItemTests.swift */; };
- 3D04215520C2D6A100DA5126 /* WhatsNewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04214D20C2D6A100DA5126 /* WhatsNewTests.swift */; };
- 3D04215620C2D6A100DA5126 /* KeyValueWhatsNewVersionStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04214F20C2D6A100DA5126 /* KeyValueWhatsNewVersionStoreTests.swift */; };
- 3D04215720C2D6A100DA5126 /* InMemoryWhatsNewVersionStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D04215020C2D6A100DA5126 /* InMemoryWhatsNewVersionStoreTests.swift */; };
- 3D13455E20BC8AFA00BE0FF8 /* WhatsNewViewController+HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13455D20BC8AFA00BE0FF8 /* WhatsNewViewController+HapticFeedback.swift */; };
- 3D2412C720BADBDC005A04D6 /* KeyValueWhatsNewVersionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2412C620BADBDC005A04D6 /* KeyValueWhatsNewVersionStore.swift */; };
- 3D2412CF20BAE75E005A04D6 /* WhatsNewViewController+DetailButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2412CE20BAE75E005A04D6 /* WhatsNewViewController+DetailButton.swift */; };
- 3D2412D120BAE7C6005A04D6 /* WhatsNewViewController+CompletionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2412D020BAE7C6005A04D6 /* WhatsNewViewController+CompletionButton.swift */; };
- 3D2803CD22059B2200E1FA31 /* Anchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803CB220598F400E1FA31 /* Anchor.swift */; };
- 3D2803D02205A1F500E1FA31 /* WhatsNewTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803CE2205A1B800E1FA31 /* WhatsNewTitleViewController.swift */; };
- 3D2803D52205A6B600E1FA31 /* WhatsNewButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803D42205A6B600E1FA31 /* WhatsNewButtonViewController.swift */; };
- 3D2803D72205AA7F00E1FA31 /* WhatsNewButtonViewController+DetailButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803D62205AA7F00E1FA31 /* WhatsNewButtonViewController+DetailButton.swift */; };
- 3D2803D92205ABAB00E1FA31 /* WhatsNewButtonViewController+CompletionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803D82205ABAB00E1FA31 /* WhatsNewButtonViewController+CompletionButton.swift */; };
- 3D2803DB2205AE5C00E1FA31 /* WhatsNewItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803DA2205AE5C00E1FA31 /* WhatsNewItemsViewController.swift */; };
- 3D2803DD2205B4E200E1FA31 /* WhatsNewItemsViewController+Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803DC2205B4E200E1FA31 /* WhatsNewItemsViewController+Cell.swift */; };
- 3D2803E62205D4E800E1FA31 /* VersionStoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2803E52205D4E800E1FA31 /* VersionStoreConfiguration.swift */; };
- 3D299144217B5D8100132C91 /* ExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299143217B5D8100132C91 /* ExampleViewController.swift */; };
- 3D299146217B5EDD00132C91 /* ConfigurationsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299145217B5EDD00132C91 /* ConfigurationsTableViewCell.swift */; };
- 3D299148217B5FC700132C91 /* ConfigurationCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299147217B5FC700132C91 /* ConfigurationCollectionViewCell.swift */; };
- 3D29914A217B691100132C91 /* ExampleViewController+PresentWhatsNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299149217B691100132C91 /* ExampleViewController+PresentWhatsNew.swift */; };
- 3D29914E217B6E7100132C91 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D29914D217B6E7100132C91 /* Configuration.swift */; };
- 3D299150217B6E8500132C91 /* BackgroundColorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D29914F217B6E8500132C91 /* BackgroundColorConfiguration.swift */; };
- 3D299152217B6E9600132C91 /* TintColorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299151217B6E9600132C91 /* TintColorConfiguration.swift */; };
- 3D299154217B6EA600132C91 /* AnimationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299153217B6EA600132C91 /* AnimationConfiguration.swift */; };
- 3D299156217B6EBA00132C91 /* LayoutConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299155217B6EBA00132C91 /* LayoutConfiguration.swift */; };
- 3D29915D217B6FCD00132C91 /* Collection+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D29915C217B6FCD00132C91 /* Collection+Safe.swift */; };
- 3D29915F217B720400132C91 /* ReuseIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D29915E217B720400132C91 /* ReuseIdentifiable.swift */; };
- 3D299161217B7EEB00132C91 /* UIImage+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D299160217B7EEB00132C91 /* UIImage+Assets.swift */; };
- 3D330EC4217B8E8E002359E7 /* HapticFeedbackConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D330EC3217B8E8E002359E7 /* HapticFeedbackConfiguration.swift */; };
- 3D33221D21C416E900B2949F /* SecondaryTitleColorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D33221C21C416E900B2949F /* SecondaryTitleColorConfiguration.swift */; };
- 3D375BA020BC6582005DE31B /* UIColor+Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D375B9F20BC6582005DE31B /* UIColor+Main.swift */; };
- 3D562CB920B3FD3200603182 /* WhatsNew+Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D562CB820B3FD3200603182 /* WhatsNew+Version.swift */; };
- 3D5AA3BD252078C8002F6467 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5AA3BC252078C8002F6467 /* Disposable.swift */; };
- 3D5AA3C0252078E6002F6467 /* DisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5AA3BF252078E6002F6467 /* DisposeBag.swift */; };
- 3D5D06BD20C7C9C700EC7C46 /* WhatsNewViewController+TitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5D06BC20C7C9C700EC7C46 /* WhatsNewViewController+TitleView.swift */; };
- 3D5D06BF20C7CA2700EC7C46 /* WhatsNewViewController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5D06BE20C7CA2700EC7C46 /* WhatsNewViewController+Animation.swift */; };
- 3D5D06C120C7CAEC00EC7C46 /* WhatsNewViewController+ItemsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5D06C020C7CAEC00EC7C46 /* WhatsNewViewController+ItemsView.swift */; };
- 3D7E00C420B6A5F10036BDB1 /* WhatsNewViewController+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D7E00C320B6A5F10036BDB1 /* WhatsNewViewController+Configuration.swift */; };
- 3D92C66A20AF87E600A10333 /* WhatsNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D92C66920AF87E600A10333 /* WhatsNew.swift */; };
- 3D92C66E20AF87F500A10333 /* WhatsNew+Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D92C66D20AF87F500A10333 /* WhatsNew+Item.swift */; };
- 3D92C67120AF883300A10333 /* WhatsNewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D92C67020AF883300A10333 /* WhatsNewViewController.swift */; };
- 3DA6679C20B0BB3B00DB18FF /* WhatsNewKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* WhatsNewKit.framework */; };
- 3DA6679D20B0BB3B00DB18FF /* WhatsNewKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* WhatsNewKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 3DB507B924420E6C0014A479 /* UIView+MakeConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB507B824420E6C0014A479 /* UIView+MakeConstraints.swift */; };
- 3DBE9416236D7A6E0026B0FB /* WhatsNewViewController+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE9414236D7A480026B0FB /* WhatsNewViewController+Theme.swift */; };
- 3DBFB67620B6EBC100FF0899 /* InMemoryWhatsNewVersionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBFB67520B6EBC100FF0899 /* InMemoryWhatsNewVersionStore.swift */; };
- 3DC8A6AB20AED02400292C94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC8A6AA20AED02400292C94 /* AppDelegate.swift */; };
- 3DC8A6B220AED02600292C94 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3DC8A6B120AED02600292C94 /* Assets.xcassets */; };
- 3DC8A6B520AED02600292C94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3DC8A6B320AED02600292C94 /* LaunchScreen.storyboard */; };
- 3DDDB59720B832AB001D59C5 /* UIColor+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDDB59620B832AB001D59C5 /* UIColor+Defaults.swift */; };
- 3DF12C2B2210AE41006473F0 /* ContentModeConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF12C2A2210AE41006473F0 /* ContentModeConfiguration.swift */; };
- 3DF12C2D2210AF51006473F0 /* ItemsCountConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF12C2C2210AF51006473F0 /* ItemsCountConfiguration.swift */; };
- 3DFB700020B34F8800673FD6 /* WhatsNewVersionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFB6FFF20B34F8800673FD6 /* WhatsNewVersionStore.swift */; };
- 52D6D9871BEFF229002C0205 /* WhatsNewKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* WhatsNewKit.framework */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 3DA6679E20B0BB3B00DB18FF /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 52D6D97B1BEFF229002C0205;
- remoteInfo = "WhatsNewKit-iOS";
- };
- 52D6D9881BEFF229002C0205 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 52D6D9731BEFF229002C0205 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 52D6D97B1BEFF229002C0205;
- remoteInfo = WhatsNewKit;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 3DA667A020B0BB3B00DB18FF /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 3DA6679D20B0BB3B00DB18FF /* WhatsNewKit.framework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 2264FE182441B2630030A4CE /* TitleModeConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleModeConfiguration.swift; sourceTree = ""; };
- 3D04214520C2D68C00DA5126 /* BaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTests.swift; sourceTree = ""; };
- 3D04214720C2D6A000DA5126 /* WhatsNewViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatsNewViewControllerTests.swift; sourceTree = ""; };
- 3D04214B20C2D6A100DA5126 /* WhatsNewVersionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatsNewVersionTests.swift; sourceTree = ""; };
- 3D04214C20C2D6A100DA5126 /* WhatsNewItemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatsNewItemTests.swift; sourceTree = ""; };
- 3D04214D20C2D6A100DA5126 /* WhatsNewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatsNewTests.swift; sourceTree = ""; };
- 3D04214F20C2D6A100DA5126 /* KeyValueWhatsNewVersionStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueWhatsNewVersionStoreTests.swift; sourceTree = ""; };
- 3D04215020C2D6A100DA5126 /* InMemoryWhatsNewVersionStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryWhatsNewVersionStoreTests.swift; sourceTree = ""; };
- 3D13455D20BC8AFA00BE0FF8 /* WhatsNewViewController+HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+HapticFeedback.swift"; sourceTree = ""; };
- 3D2412C620BADBDC005A04D6 /* KeyValueWhatsNewVersionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueWhatsNewVersionStore.swift; sourceTree = ""; };
- 3D2412CE20BAE75E005A04D6 /* WhatsNewViewController+DetailButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+DetailButton.swift"; sourceTree = ""; };
- 3D2412D020BAE7C6005A04D6 /* WhatsNewViewController+CompletionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+CompletionButton.swift"; sourceTree = ""; };
- 3D2412E620BAEB56005A04D6 /* .jazzy.yaml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .jazzy.yaml; sourceTree = SOURCE_ROOT; };
- 3D2412E720BAEB56005A04D6 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; };
- 3D2412E920BAEB56005A04D6 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = SOURCE_ROOT; };
- 3D2412EA20BAEB56005A04D6 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
- 3D2412EB20BAEB56005A04D6 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = SOURCE_ROOT; };
- 3D2412EC20BAEB56005A04D6 /* WhatsNewKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = WhatsNewKit.podspec; sourceTree = SOURCE_ROOT; };
- 3D2803CB220598F400E1FA31 /* Anchor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Anchor.swift; sourceTree = ""; };
- 3D2803CE2205A1B800E1FA31 /* WhatsNewTitleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewTitleViewController.swift; sourceTree = ""; };
- 3D2803D42205A6B600E1FA31 /* WhatsNewButtonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewButtonViewController.swift; sourceTree = ""; };
- 3D2803D62205AA7F00E1FA31 /* WhatsNewButtonViewController+DetailButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewButtonViewController+DetailButton.swift"; sourceTree = ""; };
- 3D2803D82205ABAB00E1FA31 /* WhatsNewButtonViewController+CompletionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewButtonViewController+CompletionButton.swift"; sourceTree = ""; };
- 3D2803DA2205AE5C00E1FA31 /* WhatsNewItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewItemsViewController.swift; sourceTree = ""; };
- 3D2803DC2205B4E200E1FA31 /* WhatsNewItemsViewController+Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewItemsViewController+Cell.swift"; sourceTree = ""; };
- 3D2803E52205D4E800E1FA31 /* VersionStoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionStoreConfiguration.swift; sourceTree = ""; };
- 3D299143217B5D8100132C91 /* ExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleViewController.swift; sourceTree = ""; };
- 3D299145217B5EDD00132C91 /* ConfigurationsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationsTableViewCell.swift; sourceTree = ""; };
- 3D299147217B5FC700132C91 /* ConfigurationCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationCollectionViewCell.swift; sourceTree = ""; };
- 3D299149217B691100132C91 /* ExampleViewController+PresentWhatsNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ExampleViewController+PresentWhatsNew.swift"; sourceTree = ""; };
- 3D29914D217B6E7100132C91 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; };
- 3D29914F217B6E8500132C91 /* BackgroundColorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundColorConfiguration.swift; sourceTree = ""; };
- 3D299151217B6E9600132C91 /* TintColorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColorConfiguration.swift; sourceTree = ""; };
- 3D299153217B6EA600132C91 /* AnimationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationConfiguration.swift; sourceTree = ""; };
- 3D299155217B6EBA00132C91 /* LayoutConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutConfiguration.swift; sourceTree = ""; };
- 3D29915C217B6FCD00132C91 /* Collection+Safe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Safe.swift"; sourceTree = ""; };
- 3D29915E217B720400132C91 /* ReuseIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReuseIdentifiable.swift; sourceTree = ""; };
- 3D299160217B7EEB00132C91 /* UIImage+Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Assets.swift"; sourceTree = ""; };
- 3D330EC3217B8E8E002359E7 /* HapticFeedbackConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedbackConfiguration.swift; sourceTree = ""; };
- 3D33221C21C416E900B2949F /* SecondaryTitleColorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryTitleColorConfiguration.swift; sourceTree = ""; };
- 3D375B9F20BC6582005DE31B /* UIColor+Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Main.swift"; sourceTree = ""; };
- 3D562CB820B3FD3200603182 /* WhatsNew+Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNew+Version.swift"; sourceTree = ""; };
- 3D5AA3BC252078C8002F6467 /* Disposable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; };
- 3D5AA3BF252078E6002F6467 /* DisposeBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposeBag.swift; sourceTree = ""; };
- 3D5D06BC20C7C9C700EC7C46 /* WhatsNewViewController+TitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+TitleView.swift"; sourceTree = ""; };
- 3D5D06BE20C7CA2700EC7C46 /* WhatsNewViewController+Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+Animation.swift"; sourceTree = ""; };
- 3D5D06C020C7CAEC00EC7C46 /* WhatsNewViewController+ItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+ItemsView.swift"; sourceTree = ""; };
- 3D7E00C320B6A5F10036BDB1 /* WhatsNewViewController+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+Configuration.swift"; sourceTree = ""; };
- 3D914522237DC191006EAF1A /* main.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; name = main.yml; path = .github/workflows/main.yml; sourceTree = SOURCE_ROOT; };
- 3D92C66920AF87E600A10333 /* WhatsNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNew.swift; sourceTree = ""; };
- 3D92C66D20AF87F500A10333 /* WhatsNew+Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNew+Item.swift"; sourceTree = ""; };
- 3D92C67020AF883300A10333 /* WhatsNewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewViewController.swift; sourceTree = ""; };
- 3DAEF0172328469B00A61D87 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; };
- 3DB507B824420E6C0014A479 /* UIView+MakeConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+MakeConstraints.swift"; sourceTree = ""; };
- 3DBE9414236D7A480026B0FB /* WhatsNewViewController+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WhatsNewViewController+Theme.swift"; sourceTree = ""; };
- 3DBFB67520B6EBC100FF0899 /* InMemoryWhatsNewVersionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryWhatsNewVersionStore.swift; sourceTree = ""; };
- 3DC8A6A820AED02400292C94 /* WhatsNewKit-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WhatsNewKit-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
- 3DC8A6AA20AED02400292C94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 3DC8A6B120AED02600292C94 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 3DC8A6B420AED02600292C94 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 3DC8A6B620AED02600292C94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 3DDDB59620B832AB001D59C5 /* UIColor+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Defaults.swift"; sourceTree = ""; };
- 3DF12C2A2210AE41006473F0 /* ContentModeConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentModeConfiguration.swift; sourceTree = ""; };
- 3DF12C2C2210AF51006473F0 /* ItemsCountConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsCountConfiguration.swift; sourceTree = ""; };
- 3DFB6FFF20B34F8800673FD6 /* WhatsNewVersionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewVersionStore.swift; sourceTree = ""; };
- 52D6D97C1BEFF229002C0205 /* WhatsNewKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WhatsNewKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 52D6D9861BEFF229002C0205 /* WhatsNewKit-iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "WhatsNewKit-iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
- AD2FAA261CD0B6D800659CF4 /* WhatsNewKit.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = WhatsNewKit.plist; sourceTree = ""; };
- AD2FAA281CD0B6E100659CF4 /* WhatsNewKitTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = WhatsNewKitTests.plist; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 3DC8A6A520AED02400292C94 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 3DA6679C20B0BB3B00DB18FF /* WhatsNewKit.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 52D6D9781BEFF229002C0205 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 52D6D9831BEFF229002C0205 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 52D6D9871BEFF229002C0205 /* WhatsNewKit.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 3D04214A20C2D6A100DA5126 /* Models */ = {
- isa = PBXGroup;
- children = (
- 3D04214B20C2D6A100DA5126 /* WhatsNewVersionTests.swift */,
- 3D04214C20C2D6A100DA5126 /* WhatsNewItemTests.swift */,
- 3D04214D20C2D6A100DA5126 /* WhatsNewTests.swift */,
- );
- path = Models;
- sourceTree = "";
- };
- 3D04214E20C2D6A100DA5126 /* Stores */ = {
- isa = PBXGroup;
- children = (
- 3D04214F20C2D6A100DA5126 /* KeyValueWhatsNewVersionStoreTests.swift */,
- 3D04215020C2D6A100DA5126 /* InMemoryWhatsNewVersionStoreTests.swift */,
- );
- path = Stores;
- sourceTree = "";
- };
- 3D29914B217B6E2000132C91 /* ExampleViewController */ = {
- isa = PBXGroup;
- children = (
- 3D299143217B5D8100132C91 /* ExampleViewController.swift */,
- 3D299149217B691100132C91 /* ExampleViewController+PresentWhatsNew.swift */,
- 3D299145217B5EDD00132C91 /* ConfigurationsTableViewCell.swift */,
- 3D299147217B5FC700132C91 /* ConfigurationCollectionViewCell.swift */,
- );
- path = ExampleViewController;
- sourceTree = "";
- };
- 3D29914C217B6E6200132C91 /* Configurations */ = {
- isa = PBXGroup;
- children = (
- 3D29914D217B6E7100132C91 /* Configuration.swift */,
- 3D29914F217B6E8500132C91 /* BackgroundColorConfiguration.swift */,
- 3D299151217B6E9600132C91 /* TintColorConfiguration.swift */,
- 3D299153217B6EA600132C91 /* AnimationConfiguration.swift */,
- 3D2803E52205D4E800E1FA31 /* VersionStoreConfiguration.swift */,
- 3D299155217B6EBA00132C91 /* LayoutConfiguration.swift */,
- 3DF12C2A2210AE41006473F0 /* ContentModeConfiguration.swift */,
- 3D330EC3217B8E8E002359E7 /* HapticFeedbackConfiguration.swift */,
- 3D33221C21C416E900B2949F /* SecondaryTitleColorConfiguration.swift */,
- 2264FE182441B2630030A4CE /* TitleModeConfiguration.swift */,
- 3DF12C2C2210AF51006473F0 /* ItemsCountConfiguration.swift */,
- );
- path = Configurations;
- sourceTree = "";
- };
- 3D299157217B6EDF00132C91 /* Shared */ = {
- isa = PBXGroup;
- children = (
- 3D299160217B7EEB00132C91 /* UIImage+Assets.swift */,
- 3D375B9F20BC6582005DE31B /* UIColor+Main.swift */,
- 3D29915C217B6FCD00132C91 /* Collection+Safe.swift */,
- 3D29915E217B720400132C91 /* ReuseIdentifiable.swift */,
- );
- path = Shared;
- sourceTree = "";
- };
- 3D5AA3BB252078BE002F6467 /* DataStructures */ = {
- isa = PBXGroup;
- children = (
- 3D5AA3BE252078DB002F6467 /* Disposable */,
- );
- path = DataStructures;
- sourceTree = "";
- };
- 3D5AA3BE252078DB002F6467 /* Disposable */ = {
- isa = PBXGroup;
- children = (
- 3D5AA3BC252078C8002F6467 /* Disposable.swift */,
- 3D5AA3BF252078E6002F6467 /* DisposeBag.swift */,
- );
- path = Disposable;
- sourceTree = "";
- };
- 3D92C66C20AF87EA00A10333 /* Models */ = {
- isa = PBXGroup;
- children = (
- 3D92C66920AF87E600A10333 /* WhatsNew.swift */,
- 3D562CB820B3FD3200603182 /* WhatsNew+Version.swift */,
- 3D92C66D20AF87F500A10333 /* WhatsNew+Item.swift */,
- );
- path = Models;
- sourceTree = "";
- };
- 3D92C67920AF88DC00A10333 /* Components */ = {
- isa = PBXGroup;
- children = (
- 3DA1829C20B336BF00D2C155 /* Title */,
- 3DA1829D20B336C600D2C155 /* Items */,
- 3DA1829E20B336CB00D2C155 /* Buttons */,
- );
- path = Components;
- sourceTree = "";
- };
- 3D92C67A20AF88E000A10333 /* Configuration */ = {
- isa = PBXGroup;
- children = (
- 3D7E00C320B6A5F10036BDB1 /* WhatsNewViewController+Configuration.swift */,
- 3D5D06BC20C7C9C700EC7C46 /* WhatsNewViewController+TitleView.swift */,
- 3D5D06C020C7CAEC00EC7C46 /* WhatsNewViewController+ItemsView.swift */,
- 3D2412CE20BAE75E005A04D6 /* WhatsNewViewController+DetailButton.swift */,
- 3D2412D020BAE7C6005A04D6 /* WhatsNewViewController+CompletionButton.swift */,
- 3D13455D20BC8AFA00BE0FF8 /* WhatsNewViewController+HapticFeedback.swift */,
- 3D5D06BE20C7CA2700EC7C46 /* WhatsNewViewController+Animation.swift */,
- );
- path = Configuration;
- sourceTree = "";
- };
- 3DA1829C20B336BF00D2C155 /* Title */ = {
- isa = PBXGroup;
- children = (
- 3D2803CE2205A1B800E1FA31 /* WhatsNewTitleViewController.swift */,
- );
- path = Title;
- sourceTree = "";
- };
- 3DA1829D20B336C600D2C155 /* Items */ = {
- isa = PBXGroup;
- children = (
- 3D2803DA2205AE5C00E1FA31 /* WhatsNewItemsViewController.swift */,
- 3D2803DC2205B4E200E1FA31 /* WhatsNewItemsViewController+Cell.swift */,
- );
- path = Items;
- sourceTree = "";
- };
- 3DA1829E20B336CB00D2C155 /* Buttons */ = {
- isa = PBXGroup;
- children = (
- 3D2803D42205A6B600E1FA31 /* WhatsNewButtonViewController.swift */,
- 3D2803D62205AA7F00E1FA31 /* WhatsNewButtonViewController+DetailButton.swift */,
- 3D2803D82205ABAB00E1FA31 /* WhatsNewButtonViewController+CompletionButton.swift */,
- );
- path = Buttons;
- sourceTree = "";
- };
- 3DBE9413236D7A380026B0FB /* Theme */ = {
- isa = PBXGroup;
- children = (
- 3DBE9414236D7A480026B0FB /* WhatsNewViewController+Theme.swift */,
- );
- path = Theme;
- sourceTree = "";
- };
- 3DBFB67720B6ED9300FF0899 /* Resources */ = {
- isa = PBXGroup;
- children = (
- 3DC8A6B120AED02600292C94 /* Assets.xcassets */,
- 3DC8A6B320AED02600292C94 /* LaunchScreen.storyboard */,
- 3DC8A6B620AED02600292C94 /* Info.plist */,
- );
- path = Resources;
- sourceTree = "";
- };
- 3DC8A6A920AED02400292C94 /* Example */ = {
- isa = PBXGroup;
- children = (
- 3DC8A6AA20AED02400292C94 /* AppDelegate.swift */,
- 3D29914B217B6E2000132C91 /* ExampleViewController */,
- 3D29914C217B6E6200132C91 /* Configurations */,
- 3D299157217B6EDF00132C91 /* Shared */,
- 3DBFB67720B6ED9300FF0899 /* Resources */,
- );
- path = Example;
- sourceTree = "";
- };
- 3DC8A6BA20AED37B00292C94 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- );
- name = Frameworks;
- sourceTree = "";
- };
- 3DDDB59520B83296001D59C5 /* Extensions */ = {
- isa = PBXGroup;
- children = (
- 3D2803CB220598F400E1FA31 /* Anchor.swift */,
- 3DDDB59620B832AB001D59C5 /* UIColor+Defaults.swift */,
- 3DB507B824420E6C0014A479 /* UIView+MakeConstraints.swift */,
- );
- path = Extensions;
- sourceTree = "";
- };
- 3DFB6FFE20B34F7E00673FD6 /* Store */ = {
- isa = PBXGroup;
- children = (
- 3DFB6FFF20B34F8800673FD6 /* WhatsNewVersionStore.swift */,
- 3D2412C620BADBDC005A04D6 /* KeyValueWhatsNewVersionStore.swift */,
- 3DBFB67520B6EBC100FF0899 /* InMemoryWhatsNewVersionStore.swift */,
- );
- path = Store;
- sourceTree = "";
- };
- 52D6D9721BEFF229002C0205 = {
- isa = PBXGroup;
- children = (
- 8933C7811EB5B7E0000D00A4 /* Sources */,
- 8933C7831EB5B7EB000D00A4 /* Tests */,
- 3DC8A6A920AED02400292C94 /* Example */,
- 52D6D99C1BEFF38C002C0205 /* Configs */,
- 52D6D97D1BEFF229002C0205 /* Products */,
- 3DC8A6BA20AED37B00292C94 /* Frameworks */,
- );
- sourceTree = "";
- };
- 52D6D97D1BEFF229002C0205 /* Products */ = {
- isa = PBXGroup;
- children = (
- 52D6D97C1BEFF229002C0205 /* WhatsNewKit.framework */,
- 52D6D9861BEFF229002C0205 /* WhatsNewKit-iOS Tests.xctest */,
- 3DC8A6A820AED02400292C94 /* WhatsNewKit-Example.app */,
- );
- name = Products;
- sourceTree = "";
- };
- 52D6D99C1BEFF38C002C0205 /* Configs */ = {
- isa = PBXGroup;
- children = (
- DD7502721C68FC1B006590AF /* Frameworks */,
- DD7502731C68FC20006590AF /* Tests */,
- 3D2412E920BAEB56005A04D6 /* .gitignore */,
- 3D914522237DC191006EAF1A /* main.yml */,
- 3D2412E620BAEB56005A04D6 /* .jazzy.yaml */,
- 3D2412EB20BAEB56005A04D6 /* .swiftlint.yml */,
- 3D2412E720BAEB56005A04D6 /* LICENSE */,
- 3D2412EA20BAEB56005A04D6 /* README.md */,
- 3DAEF0172328469B00A61D87 /* Package.swift */,
- 3D2412EC20BAEB56005A04D6 /* WhatsNewKit.podspec */,
- );
- path = Configs;
- sourceTree = "";
- };
- 8933C7811EB5B7E0000D00A4 /* Sources */ = {
- isa = PBXGroup;
- children = (
- 3D92C67020AF883300A10333 /* WhatsNewViewController.swift */,
- 3D92C67A20AF88E000A10333 /* Configuration */,
- 3DBE9413236D7A380026B0FB /* Theme */,
- 3DFB6FFE20B34F7E00673FD6 /* Store */,
- 3D92C67920AF88DC00A10333 /* Components */,
- 3D92C66C20AF87EA00A10333 /* Models */,
- 3D5AA3BB252078BE002F6467 /* DataStructures */,
- 3DDDB59520B83296001D59C5 /* Extensions */,
- );
- path = Sources;
- sourceTree = "";
- };
- 8933C7831EB5B7EB000D00A4 /* Tests */ = {
- isa = PBXGroup;
- children = (
- 3D04214520C2D68C00DA5126 /* BaseTests.swift */,
- 3D04214720C2D6A000DA5126 /* WhatsNewViewControllerTests.swift */,
- 3D04214A20C2D6A100DA5126 /* Models */,
- 3D04214E20C2D6A100DA5126 /* Stores */,
- );
- path = Tests;
- sourceTree = "";
- };
- DD7502721C68FC1B006590AF /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- AD2FAA261CD0B6D800659CF4 /* WhatsNewKit.plist */,
- );
- name = Frameworks;
- sourceTree = "";
- };
- DD7502731C68FC20006590AF /* Tests */ = {
- isa = PBXGroup;
- children = (
- AD2FAA281CD0B6E100659CF4 /* WhatsNewKitTests.plist */,
- );
- name = Tests;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXHeadersBuildPhase section */
- 52D6D9791BEFF229002C0205 /* Headers */ = {
- isa = PBXHeadersBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXHeadersBuildPhase section */
-
-/* Begin PBXNativeTarget section */
- 3DC8A6A720AED02400292C94 /* WhatsNewKit-Example */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 3DC8A6B720AED02600292C94 /* Build configuration list for PBXNativeTarget "WhatsNewKit-Example" */;
- buildPhases = (
- 3DC8A6A420AED02400292C94 /* Sources */,
- 3DC8A6A520AED02400292C94 /* Frameworks */,
- 3DC8A6A620AED02400292C94 /* Resources */,
- 3DA667A020B0BB3B00DB18FF /* Embed Frameworks */,
- );
- buildRules = (
- );
- dependencies = (
- 3DA6679F20B0BB3B00DB18FF /* PBXTargetDependency */,
- );
- name = "WhatsNewKit-Example";
- productName = Example;
- productReference = 3DC8A6A820AED02400292C94 /* WhatsNewKit-Example.app */;
- productType = "com.apple.product-type.application";
- };
- 52D6D97B1BEFF229002C0205 /* WhatsNewKit-iOS */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "WhatsNewKit-iOS" */;
- buildPhases = (
- 52D6D9771BEFF229002C0205 /* Sources */,
- 52D6D9781BEFF229002C0205 /* Frameworks */,
- 52D6D9791BEFF229002C0205 /* Headers */,
- 52D6D97A1BEFF229002C0205 /* Resources */,
- 3DFB700320B3591E00673FD6 /* SwiftLint */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = "WhatsNewKit-iOS";
- productName = WhatsNewKit;
- productReference = 52D6D97C1BEFF229002C0205 /* WhatsNewKit.framework */;
- productType = "com.apple.product-type.framework";
- };
- 52D6D9851BEFF229002C0205 /* WhatsNewKit-iOS Tests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 52D6D9931BEFF229002C0205 /* Build configuration list for PBXNativeTarget "WhatsNewKit-iOS Tests" */;
- buildPhases = (
- 52D6D9821BEFF229002C0205 /* Sources */,
- 52D6D9831BEFF229002C0205 /* Frameworks */,
- 52D6D9841BEFF229002C0205 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 52D6D9891BEFF229002C0205 /* PBXTargetDependency */,
- );
- name = "WhatsNewKit-iOS Tests";
- productName = WhatsNewKitTests;
- productReference = 52D6D9861BEFF229002C0205 /* WhatsNewKit-iOS Tests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 52D6D9731BEFF229002C0205 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- LastSwiftUpdateCheck = 0930;
- LastUpgradeCheck = 1200;
- ORGANIZATIONNAME = WhatsNewKit;
- TargetAttributes = {
- 3DC8A6A720AED02400292C94 = {
- CreatedOnToolsVersion = 9.3.1;
- ProvisioningStyle = Automatic;
- };
- 52D6D97B1BEFF229002C0205 = {
- CreatedOnToolsVersion = 7.1;
- LastSwiftMigration = 0800;
- };
- 52D6D9851BEFF229002C0205 = {
- CreatedOnToolsVersion = 7.1;
- LastSwiftMigration = 0940;
- };
- };
- };
- buildConfigurationList = 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "WhatsNewKit" */;
- compatibilityVersion = "Xcode 6.3";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 52D6D9721BEFF229002C0205;
- productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 52D6D97B1BEFF229002C0205 /* WhatsNewKit-iOS */,
- 52D6D9851BEFF229002C0205 /* WhatsNewKit-iOS Tests */,
- 3DC8A6A720AED02400292C94 /* WhatsNewKit-Example */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 3DC8A6A620AED02400292C94 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 3DC8A6B520AED02600292C94 /* LaunchScreen.storyboard in Resources */,
- 3DC8A6B220AED02600292C94 /* Assets.xcassets in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 52D6D97A1BEFF229002C0205 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 52D6D9841BEFF229002C0205 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 3DFB700320B3591E00673FD6 /* SwiftLint */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = SwiftLint;
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 3DC8A6A420AED02400292C94 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 3D299148217B5FC700132C91 /* ConfigurationCollectionViewCell.swift in Sources */,
- 3D2803E62205D4E800E1FA31 /* VersionStoreConfiguration.swift in Sources */,
- 3D29915F217B720400132C91 /* ReuseIdentifiable.swift in Sources */,
- 3D29914A217B691100132C91 /* ExampleViewController+PresentWhatsNew.swift in Sources */,
- 3DC8A6AB20AED02400292C94 /* AppDelegate.swift in Sources */,
- 3D299152217B6E9600132C91 /* TintColorConfiguration.swift in Sources */,
- 2264FE192441B2630030A4CE /* TitleModeConfiguration.swift in Sources */,
- 3D375BA020BC6582005DE31B /* UIColor+Main.swift in Sources */,
- 3D299150217B6E8500132C91 /* BackgroundColorConfiguration.swift in Sources */,
- 3D299146217B5EDD00132C91 /* ConfigurationsTableViewCell.swift in Sources */,
- 3D29914E217B6E7100132C91 /* Configuration.swift in Sources */,
- 3D299154217B6EA600132C91 /* AnimationConfiguration.swift in Sources */,
- 3D33221D21C416E900B2949F /* SecondaryTitleColorConfiguration.swift in Sources */,
- 3D299156217B6EBA00132C91 /* LayoutConfiguration.swift in Sources */,
- 3D29915D217B6FCD00132C91 /* Collection+Safe.swift in Sources */,
- 3D299161217B7EEB00132C91 /* UIImage+Assets.swift in Sources */,
- 3D299144217B5D8100132C91 /* ExampleViewController.swift in Sources */,
- 3DF12C2D2210AF51006473F0 /* ItemsCountConfiguration.swift in Sources */,
- 3D330EC4217B8E8E002359E7 /* HapticFeedbackConfiguration.swift in Sources */,
- 3DF12C2B2210AE41006473F0 /* ContentModeConfiguration.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 52D6D9771BEFF229002C0205 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 3D2803DB2205AE5C00E1FA31 /* WhatsNewItemsViewController.swift in Sources */,
- 3D7E00C420B6A5F10036BDB1 /* WhatsNewViewController+Configuration.swift in Sources */,
- 3D5AA3C0252078E6002F6467 /* DisposeBag.swift in Sources */,
- 3DBE9416236D7A6E0026B0FB /* WhatsNewViewController+Theme.swift in Sources */,
- 3D5D06BD20C7C9C700EC7C46 /* WhatsNewViewController+TitleView.swift in Sources */,
- 3D2803D02205A1F500E1FA31 /* WhatsNewTitleViewController.swift in Sources */,
- 3D2803D72205AA7F00E1FA31 /* WhatsNewButtonViewController+DetailButton.swift in Sources */,
- 3D5AA3BD252078C8002F6467 /* Disposable.swift in Sources */,
- 3D2803CD22059B2200E1FA31 /* Anchor.swift in Sources */,
- 3DDDB59720B832AB001D59C5 /* UIColor+Defaults.swift in Sources */,
- 3D2803DD2205B4E200E1FA31 /* WhatsNewItemsViewController+Cell.swift in Sources */,
- 3D92C66A20AF87E600A10333 /* WhatsNew.swift in Sources */,
- 3DFB700020B34F8800673FD6 /* WhatsNewVersionStore.swift in Sources */,
- 3DBFB67620B6EBC100FF0899 /* InMemoryWhatsNewVersionStore.swift in Sources */,
- 3D13455E20BC8AFA00BE0FF8 /* WhatsNewViewController+HapticFeedback.swift in Sources */,
- 3D92C67120AF883300A10333 /* WhatsNewViewController.swift in Sources */,
- 3D2412CF20BAE75E005A04D6 /* WhatsNewViewController+DetailButton.swift in Sources */,
- 3D5D06C120C7CAEC00EC7C46 /* WhatsNewViewController+ItemsView.swift in Sources */,
- 3D92C66E20AF87F500A10333 /* WhatsNew+Item.swift in Sources */,
- 3D2412C720BADBDC005A04D6 /* KeyValueWhatsNewVersionStore.swift in Sources */,
- 3D2803D92205ABAB00E1FA31 /* WhatsNewButtonViewController+CompletionButton.swift in Sources */,
- 3D5D06BF20C7CA2700EC7C46 /* WhatsNewViewController+Animation.swift in Sources */,
- 3D2803D52205A6B600E1FA31 /* WhatsNewButtonViewController.swift in Sources */,
- 3DB507B924420E6C0014A479 /* UIView+MakeConstraints.swift in Sources */,
- 3D562CB920B3FD3200603182 /* WhatsNew+Version.swift in Sources */,
- 3D2412D120BAE7C6005A04D6 /* WhatsNewViewController+CompletionButton.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 52D6D9821BEFF229002C0205 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 3D04215620C2D6A100DA5126 /* KeyValueWhatsNewVersionStoreTests.swift in Sources */,
- 3D04215720C2D6A100DA5126 /* InMemoryWhatsNewVersionStoreTests.swift in Sources */,
- 3D04215520C2D6A100DA5126 /* WhatsNewTests.swift in Sources */,
- 3D04214620C2D68C00DA5126 /* BaseTests.swift in Sources */,
- 3D04215120C2D6A100DA5126 /* WhatsNewViewControllerTests.swift in Sources */,
- 3D04215320C2D6A100DA5126 /* WhatsNewVersionTests.swift in Sources */,
- 3D04215420C2D6A100DA5126 /* WhatsNewItemTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 3DA6679F20B0BB3B00DB18FF /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 52D6D97B1BEFF229002C0205 /* WhatsNewKit-iOS */;
- targetProxy = 3DA6679E20B0BB3B00DB18FF /* PBXContainerItemProxy */;
- };
- 52D6D9891BEFF229002C0205 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 52D6D97B1BEFF229002C0205 /* WhatsNewKit-iOS */;
- targetProxy = 52D6D9881BEFF229002C0205 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 3DC8A6B320AED02600292C94 /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 3DC8A6B420AED02600292C94 /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 3DC8A6B820AED02600292C94 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = "";
- GCC_C_LANGUAGE_STANDARD = gnu11;
- INFOPLIST_FILE = "$(SRCROOT)/Example/Resources/Info.plist";
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- OTHER_LDFLAGS = "";
- PRODUCT_BUNDLE_IDENTIFIER = de.tiigi.WhatsNewKit.Example;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 3DC8A6B920AED02600292C94 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = "";
- GCC_C_LANGUAGE_STANDARD = gnu11;
- INFOPLIST_FILE = "$(SRCROOT)/Example/Resources/Info.plist";
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- OTHER_LDFLAGS = "";
- PRODUCT_BUNDLE_IDENTIFIER = de.tiigi.WhatsNewKit.Example;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Release;
- };
- 52D6D98E1BEFF229002C0205 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = 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_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_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- 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 = 9.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- VERSIONING_SYSTEM = "apple-generic";
- VERSION_INFO_PREFIX = "";
- };
- name = Debug;
- };
- 52D6D98F1BEFF229002C0205 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = 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_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_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- 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 = 9.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- VERSIONING_SYSTEM = "apple-generic";
- VERSION_INFO_PREFIX = "";
- };
- name = Release;
- };
- 52D6D9911BEFF229002C0205 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- APPLICATION_EXTENSION_API_ONLY = YES;
- CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_IDENTITY = "";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
- DEFINES_MODULE = YES;
- DEVELOPMENT_TEAM = "";
- DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 1;
- DYLIB_INSTALL_NAME_BASE = "@rpath";
- FRAMEWORK_SEARCH_PATHS = "$(inherited)";
- INFOPLIST_FILE = Configs/WhatsNewKit.plist;
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- ONLY_ACTIVE_ARCH = YES;
- OTHER_LDFLAGS = "";
- OTHER_SWIFT_FLAGS = "";
- PRODUCT_BUNDLE_IDENTIFIER = "de.tiigi.WhatsNewKit.WhatsNewKit-iOS";
- PRODUCT_NAME = WhatsNewKit;
- SKIP_INSTALL = YES;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- };
- name = Debug;
- };
- 52D6D9921BEFF229002C0205 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- APPLICATION_EXTENSION_API_ONLY = YES;
- CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_IDENTITY = "";
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
- DEFINES_MODULE = YES;
- DEVELOPMENT_TEAM = "";
- DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 1;
- DYLIB_INSTALL_NAME_BASE = "@rpath";
- FRAMEWORK_SEARCH_PATHS = "$(inherited)";
- INFOPLIST_FILE = Configs/WhatsNewKit.plist;
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- OTHER_LDFLAGS = "";
- OTHER_SWIFT_FLAGS = "";
- PRODUCT_BUNDLE_IDENTIFIER = "de.tiigi.WhatsNewKit.WhatsNewKit-iOS";
- PRODUCT_NAME = WhatsNewKit;
- SKIP_INSTALL = YES;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 5.0;
- };
- name = Release;
- };
- 52D6D9941BEFF229002C0205 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CLANG_ENABLE_MODULES = YES;
- INFOPLIST_FILE = Configs/WhatsNewKitTests.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- PRODUCT_BUNDLE_IDENTIFIER = "com.WhatsNewKit.WhatsNewKit-iOS-Tests";
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- };
- name = Debug;
- };
- 52D6D9951BEFF229002C0205 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CLANG_ENABLE_MODULES = YES;
- INFOPLIST_FILE = Configs/WhatsNewKitTests.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- PRODUCT_BUNDLE_IDENTIFIER = "com.WhatsNewKit.WhatsNewKit-iOS-Tests";
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 5.0;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 3DC8A6B720AED02600292C94 /* Build configuration list for PBXNativeTarget "WhatsNewKit-Example" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 3DC8A6B820AED02600292C94 /* Debug */,
- 3DC8A6B920AED02600292C94 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 52D6D9761BEFF229002C0205 /* Build configuration list for PBXProject "WhatsNewKit" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 52D6D98E1BEFF229002C0205 /* Debug */,
- 52D6D98F1BEFF229002C0205 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "WhatsNewKit-iOS" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 52D6D9911BEFF229002C0205 /* Debug */,
- 52D6D9921BEFF229002C0205 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 52D6D9931BEFF229002C0205 /* Build configuration list for PBXNativeTarget "WhatsNewKit-iOS Tests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 52D6D9941BEFF229002C0205 /* Debug */,
- 52D6D9951BEFF229002C0205 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 52D6D9731BEFF229002C0205 /* Project object */;
-}
diff --git a/fastlane/.env b/fastlane/.env
deleted file mode 100644
index ef9bcc7..0000000
--- a/fastlane/.env
+++ /dev/null
@@ -1 +0,0 @@
-FASTLANE_SKIP_UPDATE_CHECK=1
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
deleted file mode 100644
index b6a8ff8..0000000
--- a/fastlane/Fastfile
+++ /dev/null
@@ -1,86 +0,0 @@
-fastlane_version "2.120.0"
-
-default_platform :ios
-
-platform :ios do
-
- desc "Release a new version of WhatsNewKit"
- lane :release do |options|
- # Ensure Git status is clean
- ensure_git_status_clean
- # Ensure Git branch is master
- ensure_git_branch(branch: 'master')
- # Perform Dependency-Manager compatibility tests
- compatibilityTests
- # Perform Tests
- tests
- # Retrieve Version from options
- version = options[:version]
- # Increment Version
- increment(version: version)
- # Add Git Tag
- add_git_tag(tag: version)
- # Push Git Tag
- push_git_tags()
- # Push Git commit
- push_to_git_remote()
- # Pod push / Pod trunk
- pod_push()
- end
-
- desc "Increment Version"
- lane :increment do |options|
- # Retrieve Version from options
- version = options[:version]
- # Set Podspec version
- version_bump_podspec(
- path: "WhatsNewKit.podspec",
- version_number: version
- )
- # Set Framework plist version
- set_info_plist_value(
- path: "Configs/WhatsNewKit.plist",
- key: "CFBundleShortVersionString",
- value: version
- )
- # Set Framework Tests plist version
- set_info_plist_value(
- path: "Configs/WhatsNewKitTests.plist",
- key: "CFBundleShortVersionString",
- value: version
- )
- # Set Example plist version
- set_info_plist_value(
- path: "Example/Resources/Info.plist",
- key: "CFBundleShortVersionString",
- value: version
- )
- # Commit modified files
- git_commit(
- path: [
- "WhatsNewKit.podspec",
- "Configs/WhatsNewKit.plist",
- "Configs/WhatsNewKitTests.plist",
- "Example/Resources/Info.plist"
- ],
- message: "WhatsNewKit Version #{version} 🚀"
- )
- end
-
- desc "Runs tests"
- lane :tests do
- # Perform iOS Tests
- scan(
- project: "WhatsNewKit.xcodeproj",
- scheme: "WhatsNewKit-iOS",
- clean: true
- )
- end
-
- desc "Run Dependency-Manager compatibility tests"
- lane :compatibilityTests do
- # Pod lib lint to ensure CocoaPods compatibility
- pod_lib_lint(allow_warnings: true)
- end
-
-end