├── LICENSE
├── Package.swift
├── README.md
└── UpdateKit
├── Package.swift
└── Sources
└── UpdateKit
├── UpdateClasses.swift
├── UpdateKit.swift
├── UpdateNotes.swift
├── UpdateViewExamples.swift
├── UpdateViewHandler.swift
└── UpdateViewHandler2.swift
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 TheiPhoneDev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 6.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "UpdateKit",
8 | platforms: [
9 | .iOS(.v15),
10 | .visionOS(.v1)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, making them visible to other packages.
14 | .library(
15 | name: "UpdateKit",
16 | targets: ["UpdateKit"]),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package, defining a module or a test suite.
20 | // Targets can depend on other targets in this package and products from dependencies.
21 | .target(
22 | name: "UpdateKit"),
23 |
24 | ],
25 | swiftLanguageModes: [.v6]
26 | )
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UpdateKit
2 | A simple and lightweight Swift library to better present release notes in iOS, iPadOS and visionOS apps.
3 |
4 | 
5 | 
6 | 
7 | 
8 |
9 |
10 |
11 | ## Requirements
12 | **UpdateKit** requires iOS 15.0 and visionOS 1.0
13 |
14 | ## What is update kit ?
15 |
16 | **UpdateKit** is a simple and lightweight Swift library that simplifies update screens. As a developer I find myself to have to write release notes, and sometime I don't show it in the best way possible to the users. One-time update screen are the best way to show our users the new version's changes and let them know what's new and what's been fixed.
17 |
18 | ## UpdateKit structure
19 | **UpdateKit** is inspired to Apple update screens, which can be seen in iOS built-in apps. I wanted to create a simple structure that is flexible and also customizable and can be suited to the developer's needs.
20 |
21 | ## Features ✨
22 | - Customizable cells
23 | - Native UI
24 | - Integration with iOS, iPadOS and visionOS
25 | - Attach Views to show what changes have been made
26 | - Thread-safe (this means no data race) by using the Sendable protocol
27 |
28 |
29 | ## Usage
30 | To use UpdateKit, first of all:
31 | ```swift
32 | import UpdateKit
33 | ```
34 | in your project. You can implement it as SPM (Swift Package Manager).
35 | - Go to the UpdateNotes.swift file and edit the updateNotes collection. That's where the release notes are collected.
36 | - Call the UpdateViewHandler view wherever you need it.
37 |
38 | ## Implementation
39 | ```swift
40 | UpdateViewHandler(updateNotes: notes)
41 | ```
42 |
43 | ## Screenshots
44 |
45 |
46 |
47 | ## Demo 1
48 | https://github.com/user-attachments/assets/e6bee750-fa39-4261-96d2-f767abf7bd62
49 |
50 | ## Demo 2
51 | https://github.com/user-attachments/assets/f6d3ccf5-e8ee-41dc-b0d2-04dcba1206ec
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/UpdateKit/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 6.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "UpdateKit",
8 | platforms: [
9 | .iOS(.v15),
10 | .visionOS(.v1)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, making them visible to other packages.
14 | .library(
15 | name: "UpdateKit",
16 | targets: ["UpdateKit"]),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package, defining a module or a test suite.
20 | // Targets can depend on other targets in this package and products from dependencies.
21 | .target(
22 | name: "UpdateKit"),
23 |
24 | ],
25 | swiftLanguageModes: [.v6]
26 | )
27 |
--------------------------------------------------------------------------------
/UpdateKit/Sources/UpdateKit/UpdateClasses.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdateClasses.swift
3 | // UpdateKit
4 | //
5 | // Created by Pietro Gambatesa on 11/1/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 |
12 |
13 | public class Typography: ObservableObject {
14 | @Published public var sfSymbolFontType: Font = .title.weight(.semibold)
15 | @Published public var textTitleFontType: Font = .title2.weight(.semibold)
16 | @Published public var textDescriptionFontType: Font = .subheadline
17 | @Published public var textDescriptionFontColor: Color = .secondary
18 | @Published public var buttonColor: UIColor = .systemBlue
19 | @Published public var imageColor: UIColor = .systemGray
20 | @Published public var imageWidth: CGFloat = 40
21 | @Published public var imageHeight: CGFloat = 40
22 |
23 | public init() {}
24 |
25 | }
26 |
27 | public class UpdatePresenter: ObservableObject {
28 | @AppStorage("isPresentable") public var isPresentable: Bool = true
29 | public init() {}
30 |
31 | @MainActor
32 | public func setTransparentNavBar() {
33 | let appearance = UINavigationBarAppearance()
34 | appearance.configureWithTransparentBackground()
35 |
36 | UINavigationBar.appearance().scrollEdgeAppearance = appearance
37 | }
38 |
39 | @MainActor
40 | public func resetNavBarAppearance() {
41 | let appearance = UINavigationBarAppearance()
42 | appearance.configureWithDefaultBackground()
43 |
44 | UINavigationBar.appearance().standardAppearance = appearance
45 | UINavigationBar.appearance().scrollEdgeAppearance = appearance
46 | }
47 |
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/UpdateKit/Sources/UpdateKit/UpdateKit.swift:
--------------------------------------------------------------------------------
1 | // The Swift Programming Language
2 | // https://docs.swift.org/swift-book
3 |
--------------------------------------------------------------------------------
/UpdateKit/Sources/UpdateKit/UpdateNotes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdateNotes.swift
3 | // UpdateKit
4 | //
5 | // Created by Pietro Gambatesa on 10/9/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 |
12 | public struct UpdateNotes: Identifiable, Sendable {
13 | public var id = UUID()
14 | public var updateNoteTitle: String
15 | public var updateNoteDescription: String
16 | public var updateNoteImageType: String
17 | public var updateNoteImage: String
18 |
19 | public init(updateNoteImageType: String, updateNoteImage: String, updateNoteTitle: String, updateNoteDescription: String) {
20 | self.updateNoteImageType = updateNoteImageType
21 | self.updateNoteImage = updateNoteImage
22 | self.updateNoteTitle = updateNoteTitle
23 | self.updateNoteDescription = updateNoteDescription
24 | }
25 | }
26 |
27 |
28 | public let notes: [UpdateNotes] = [
29 | .init(updateNoteImageType: "Symbol", updateNoteImage: "sparkles", updateNoteTitle: "New features ✨", updateNoteDescription: "New features are available for you to use."),
30 | .init(updateNoteImageType: "Text", updateNoteImage: "🎨", updateNoteTitle: "New app icons 📱", updateNoteDescription: "New app icons are available for you to use."),
31 | .init(updateNoteImageType: "Symbol", updateNoteImage: "ant.circle.fill", updateNoteTitle: "Bug fixes", updateNoteDescription: "Bug fixes to make the app even faster"),
32 | .init(updateNoteImageType: "Text", updateNoteImage: "🌈", updateNoteTitle: "UI improvements 🌈", updateNoteDescription: "UI improvements to make the app even faster and deliver a better experience"),
33 | ]
34 |
35 |
36 | public struct UpdateNotesWithView: Identifiable, Sendable {
37 | public var id = UUID()
38 | public var updateNoteTitle: String
39 | public var updateNoteDescription: String
40 | public var updateNoteImageType: String
41 | public var updateNoteImage: String
42 | public var updateView: AnySendableView
43 | public var hasConnectedView: Bool = false
44 |
45 | public init(updateNoteImageType: String, updateNoteImage: String, updateNoteTitle: String, updateNoteDescription: String, updateView: AnySendableView, hasConnectedView: Bool) {
46 | self.updateNoteImageType = updateNoteImageType
47 | self.updateNoteImage = updateNoteImage
48 | self.updateNoteTitle = updateNoteTitle
49 | self.updateNoteDescription = updateNoteDescription
50 | self.updateView = updateView
51 | self.hasConnectedView = hasConnectedView
52 | }
53 | }
54 |
55 | @MainActor
56 | public let viewnotes: [UpdateNotesWithView] = [
57 | .init(updateNoteImageType: "Symbol", updateNoteImage: "sparkles", updateNoteTitle: "New features ✨", updateNoteDescription: "New features are available for you to use.", updateView: AnySendableView(TestView1()), hasConnectedView: true),
58 | .init(updateNoteImageType: "Text", updateNoteImage: "🎨", updateNoteTitle: "New app icons 📱", updateNoteDescription: "New app icons are available for you to use.", updateView: AnySendableView(TestView2()), hasConnectedView: true),
59 | .init(updateNoteImageType: "Symbol", updateNoteImage: "ant.circle.fill", updateNoteTitle: "Bug fixes", updateNoteDescription: "Bug fixes to make the app even faster", updateView: AnySendableView(TestView3()), hasConnectedView: true),
60 | .init(updateNoteImageType: "Text", updateNoteImage: "🌈", updateNoteTitle: "UI improvements 🌈", updateNoteDescription: "UI improvements to make the app even faster and deliver a better experience", updateView: AnySendableView(TestView1()), hasConnectedView: false),
61 | ]
62 |
63 |
64 | //Wrapper to use View with the Sendable type
65 | public struct AnySendableView: View, Sendable {
66 | private let viewBuilder: () -> AnyView
67 |
68 | // Initialize with any View
69 | public init(_ view: V) {
70 | self.viewBuilder = { AnyView(view) }
71 | }
72 |
73 | // Conforming to View
74 | public var body: some View {
75 | viewBuilder()
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/UpdateKit/Sources/UpdateKit/UpdateViewExamples.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdateViewExamples.swift
3 | // UpdateKit
4 | //
5 | // Created by Pietro Gambatesa on 11/1/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct TestView1: View {
12 | var body: some View {
13 | Text("Hello View 1")
14 | }
15 | }
16 |
17 | struct TestView2: View {
18 | var body: some View {
19 | Text("Hello View 2")
20 | }
21 | }
22 |
23 | struct TestView3: View {
24 | var body: some View {
25 | Text("Hello View 3")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/UpdateKit/Sources/UpdateKit/UpdateViewHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdateViewHandler.swift
3 | // UpdateKit
4 | //
5 | // Created by Pietro Gambatesa on 10/9/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct UpdateViewHandler: View {
11 |
12 | public var updateNotes: [UpdateNotes] = []
13 | @StateObject var typography = Typography()
14 | @StateObject var updatePresenter = UpdatePresenter()
15 | @Environment(\.presentationMode) var presentationMode
16 |
17 | public init(updateNotes: [UpdateNotes] = []) {
18 | self.updateNotes = updateNotes
19 | }
20 |
21 | public var body: some View {
22 | NavigationView(content: {
23 | ZStack {
24 | VStack {
25 | ScrollView(.vertical, showsIndicators: false, content: {
26 | VStack(spacing: 15) {
27 | ForEach(updateNotes) { note in
28 | VStack {
29 | HStack(spacing: 20) {
30 | if note.updateNoteImageType == "Image" {
31 | Image(note.updateNoteImage)
32 | .resizable()
33 | .frame(width: typography.imageWidth, height: typography.imageHeight)
34 | .cornerRadius(10)
35 | } else if note.updateNoteImageType == "Symbol" {
36 | Image(systemName: note.updateNoteImage)
37 | .font(typography.sfSymbolFontType)
38 | .foregroundColor(Color(uiColor: typography.imageColor))
39 |
40 | } else if note.updateNoteImageType == "Text" {
41 | Text(note.updateNoteImage)
42 | .font(typography.sfSymbolFontType)
43 | }
44 | VStack(alignment: .leading) {
45 | Text(note.updateNoteTitle)
46 | .font(typography.textTitleFontType)
47 | Text(note.updateNoteDescription)
48 | .font(typography.textDescriptionFontType)
49 | .foregroundColor(typography.textDescriptionFontColor)
50 |
51 | }
52 | }.padding()
53 | .frame(maxWidth: .infinity, alignment: .leading)
54 | }
55 |
56 | }
57 | }
58 |
59 | })
60 | #if os(iOS)
61 | Button {
62 | self.presentationMode.wrappedValue.dismiss()
63 | self.updatePresenter.isPresentable = false
64 | } label: {
65 | Text("Continue")
66 | .font(.title3.weight(.medium))
67 | .padding(.leading,60)
68 | .padding(.trailing,60)
69 | .padding(.bottom,10)
70 | .padding(.top,10)
71 | }.buttonStyle(.borderedProminent)
72 | .tint(Color(uiColor: typography.buttonColor))
73 | .padding(.bottom,10)
74 | #endif
75 | #if os(visionOS)
76 | Button {
77 | self.presentationMode.wrappedValue.dismiss()
78 | self.updatePresenter.isPresentable = false
79 | } label: {
80 | Text("Continue")
81 | .font(.title3.weight(.medium))
82 | .padding(.leading,60)
83 | .padding(.trailing,60)
84 | .padding(.bottom,10)
85 | .padding(.top,10)
86 | }.buttonStyle(.borderedProminent)
87 | .padding(.bottom,20)
88 | #endif
89 |
90 | }
91 | }.toolbar(content: {
92 | ToolbarItem(placement: .principal, content: {
93 | Text("What's new")
94 | .font(.title3.weight(.semibold))
95 | .frame(maxWidth: .infinity, alignment: .center)
96 | })
97 | })
98 | }).navigationViewStyle(.stack)
99 | }
100 | }
101 |
102 |
103 |
104 |
105 | #Preview {
106 | UpdateViewHandler(updateNotes: notes)
107 | }
108 |
109 |
--------------------------------------------------------------------------------
/UpdateKit/Sources/UpdateKit/UpdateViewHandler2.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UpdateViewHandler2.swift
3 | // UpdateKit
4 | //
5 | // Created by Pietro Gambatesa on 11/1/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct UpdateViewHandler2: View {
11 |
12 | public var viewNotes: [UpdateNotesWithView] = []
13 | @StateObject var typography = Typography()
14 | @StateObject var updatePresenter = UpdatePresenter()
15 | @Environment(\.presentationMode) var presentationMode
16 |
17 | public init(viewNotes: [UpdateNotesWithView] = []) {
18 | self.viewNotes = viewNotes
19 | }
20 |
21 | public var body: some View {
22 | NavigationView(content: {
23 | ZStack {
24 | VStack {
25 | ScrollView(.vertical, showsIndicators: false, content: {
26 | VStack(spacing: 15) {
27 | ForEach(viewNotes) { note in
28 | VStack {
29 | HStack(spacing: 20) {
30 | if note.updateNoteImageType == "Image" {
31 | Image(note.updateNoteImage)
32 | .resizable()
33 | .frame(width: typography.imageWidth, height: typography.imageHeight)
34 | .cornerRadius(10)
35 | } else if note.updateNoteImageType == "Symbol" {
36 | Image(systemName: note.updateNoteImage)
37 | .font(typography.sfSymbolFontType)
38 | .foregroundColor(Color(uiColor: typography.imageColor))
39 |
40 | } else if note.updateNoteImageType == "Text" {
41 | Text(note.updateNoteImage)
42 | .font(typography.sfSymbolFontType)
43 | }
44 | VStack(alignment: .leading) {
45 | Text(note.updateNoteTitle)
46 | .font(typography.textTitleFontType)
47 | Text(note.updateNoteDescription)
48 | .font(typography.textDescriptionFontType)
49 | .foregroundColor(typography.textDescriptionFontColor)
50 | if note.hasConnectedView == true {
51 | NavigationLink {
52 | note.updateView
53 | } label: {
54 | Text("See more")
55 | }.frame(maxWidth: .infinity, alignment: .trailing)
56 | }
57 | }
58 | }.padding()
59 | .frame(maxWidth: .infinity, alignment: .leading)
60 | .padding(.trailing, 20)
61 | }
62 |
63 | }
64 | }
65 |
66 | })
67 | #if os(iOS)
68 | Button {
69 | self.presentationMode.wrappedValue.dismiss()
70 | self.updatePresenter.isPresentable = false
71 | } label: {
72 | Text("Continue")
73 | .font(.title3.weight(.medium))
74 | .padding(.leading,60)
75 | .padding(.trailing,60)
76 | .padding(.bottom,10)
77 | .padding(.top,10)
78 | }.buttonStyle(.borderedProminent)
79 | .tint(Color(uiColor: typography.buttonColor))
80 | .padding(.bottom,10)
81 | #endif
82 | #if os(visionOS)
83 | Button {
84 | self.presentationMode.wrappedValue.dismiss()
85 | self.updatePresenter.isPresentable = false
86 | } label: {
87 | Text("Continue")
88 | .font(.title3.weight(.medium))
89 | .padding(.leading,60)
90 | .padding(.trailing,60)
91 | .padding(.bottom,10)
92 | .padding(.top,10)
93 | }.buttonStyle(.borderedProminent)
94 | .padding(.bottom,20)
95 | #endif
96 |
97 | }
98 | }.toolbar(content: {
99 | ToolbarItem(placement: .principal, content: {
100 | Text("What's new")
101 | .font(.title3.weight(.semibold))
102 | .frame(maxWidth: .infinity, alignment: .center)
103 | })
104 | })
105 | }).navigationViewStyle(.stack)
106 | }
107 | }
108 |
109 |
110 |
111 | #Preview {
112 | UpdateViewHandler2(viewNotes: viewnotes)
113 | }
114 |
--------------------------------------------------------------------------------