├── .gitattributes
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── ErrorableView.xcscheme
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── ErrorableView
│ ├── Abstract
│ └── ErrorableViewModifier.swift
│ ├── Enums
│ ├── ErrorPresentTypes.swift
│ └── PageStates.swift
│ └── Views
│ ├── DefaultErrorView.swift
│ └── DefaultLoadingView.swift
└── Tests
└── ErrorableViewTests
└── ErrorableViewTests.swift
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/ErrorableView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
68 |
69 |
75 |
76 |
82 |
83 |
84 |
85 |
87 |
88 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Mehmet Ateş
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: 5.8
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: "ErrorableView",
8 | platforms: [
9 | .iOS(.v15),
10 | .macOS(.v13)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, making them visible to other packages.
14 | .library(
15 | name: "ErrorableView",
16 | targets: ["ErrorableView"]),
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: "ErrorableView"),
23 | .testTarget(
24 | name: "ErrorableViewTests",
25 | dependencies: ["ErrorableView"]),
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ErrorableView-SwiftUI
2 |
3 | ## How to install this package
4 |
5 | + Open your project on Xcode
6 | + Go to the Project Tab and select "Package Dependencies"
7 | + Click "+" and search this package with use git clone URL
8 | + Don't change anything and click Add Package
9 | + The package will be attached to the targeted application
10 |
11 | ## How to use this package
12 | ### Just use The "ErrorableViewModifier" with an $pageState property
13 | ```swift
14 | struct TestView: View {
15 | @State private var pageState: PageStates = .loading
16 |
17 | var body: some View {
18 | // YOUR CODES
19 | .akErrorView(pageState: $pageState) {
20 | // TRY AGAIN ACTION
21 | }
22 | }
23 | }
24 | ```
25 | ## Useful Tips
26 | This package allows you to manage your page error state easily. But actually, it's useful. What do you get to know?
27 |
28 | - Generic Error Page Support:
29 | The Package includes a DefaultErrorPage but you don't want to use it. Use the ErrorableView protocol, and create your error page.
30 | ```swift
31 | protocol ErrorableView: View {
32 | var type: ErrorPresentTypes { get set }
33 | }
34 | ```
35 | This protocol only wants to create a type property for your error page presentation state. If your view comformed the protocol you'll use this modifier code block under the below.
36 | ```swift
37 | .modifier(ErrorableViewModifier(pageState: $viewModel.pageState) { // Like this
38 | YourView() {
39 | viewModel.reload()
40 | }
41 | })
42 | ```
43 | - Fully Customisable Error Page:
44 | The package includes a customizable ErrorPage named DefaultErrorPage. You can use that uiModel to update DefaultErrorPage.
45 | ```swift
46 | @frozen public struct DefaultErrorPageUIModel {
47 | var title: LocalizedStringKey
48 | var subtitle: LocalizedStringKey?
49 | var icon: String?
50 | var systemName: String?
51 | var buttonTitle: LocalizedStringKey?
52 | }
53 | ```
54 |
55 | ## Examples
56 | ### Sheet Type
57 | https://github.com/devmehmetates/ErrorableView-SwiftUI/assets/74152011/1fe9e28a-8ba3-48b8-8d85-b2eb4c6aa672
58 |
59 | ### OnPage Type
60 | https://github.com/devmehmetates/ErrorableView-SwiftUI/assets/74152011/2c579c96-adec-4d6e-9739-1892d97666aa
61 |
62 | ### Fullscreen Type
63 | https://github.com/devmehmetates/ErrorableView-SwiftUI/assets/74152011/6e34332f-6c24-489d-8bd2-bfd5ab2fb027
64 |
--------------------------------------------------------------------------------
/Sources/ErrorableView/Abstract/ErrorableViewModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Mehmet Ateş on 1.03.2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public extension View {
11 | @ViewBuilder
12 | func akErrorView(
13 | pageState: Binding,
14 | @ViewBuilder errorContent: () -> ErrorContent,
15 | @ViewBuilder loadingContent: () -> LoadingContent = { DefaultLoadingView(loadingText: "Loading...") }
16 | ) -> some View {
17 | self.modifier(AKErrorViewModifier(pageState: pageState) {
18 | errorContent()
19 | } loadingContent: {
20 | loadingContent()
21 | })
22 | }
23 |
24 | @ViewBuilder
25 | func akErrorView(
26 | pageState: Binding,
27 | action: @escaping () -> Void,
28 | @ViewBuilder loadingContent: () -> LoadingContent = { DefaultLoadingView(loadingText: "Loading...") }
29 | ) -> some View {
30 | self.modifier(AKErrorViewModifier(pageState: pageState) {
31 | DefaultErrorView(state: pageState) {
32 | action()
33 | }
34 | } loadingContent: {
35 | loadingContent()
36 | })
37 | }
38 |
39 | @available(*, deprecated, renamed: "akErrorView", message: "")
40 | @ViewBuilder
41 | func errorableView(pageState: Binding,
42 | @ViewBuilder content: () -> Content,
43 | @ViewBuilder loadingContent: (() -> LoadingContent) = { DefaultLoadingView(loadingText: "Loading...") }) -> some View {
44 | self.modifier(AKErrorViewModifier(pageState: pageState) {
45 | content()
46 | } loadingContent: {
47 | loadingContent()
48 | })
49 | }
50 | }
51 |
52 | public struct AKErrorViewModifier: ViewModifier {
53 | @State private var sheetTrigger: Bool = false
54 | @Binding var pageState: PageStates
55 | var errorContent: ErrorContent
56 | var loadingContent: LoadingContent
57 |
58 | public init(pageState: Binding,
59 | @ViewBuilder errorContent: () -> ErrorContent,
60 | @ViewBuilder loadingContent: () -> LoadingContent) {
61 | self._pageState = pageState
62 | self.errorContent = errorContent()
63 | self.loadingContent = loadingContent()
64 | }
65 |
66 | public func body(content: Content) -> some View {
67 | switch errorContent.type {
68 | case .onPage:
69 | onPageState(content: content)
70 | case .sheet:
71 | sheetState(content: content)
72 | case .fullScreen:
73 | sheetState(content: content)
74 | }
75 | }
76 |
77 | @ViewBuilder
78 | private func onPageState(content: Content) -> some View {
79 | switch pageState {
80 | case .failure:
81 | errorContent
82 | case .loading:
83 | switch loadingContent.type {
84 | case .onPage:
85 | loadingContent
86 | case .overlay:
87 | ZStack {
88 | content
89 | loadingContent
90 | }
91 | }
92 | DefaultLoadingView(loadingText: "Loading...")
93 | case .successful:
94 | content
95 | }
96 | }
97 |
98 | @ViewBuilder
99 | private func sheetState(content: Content) -> some View {
100 | Group {
101 | if pageState == .successful {
102 | content
103 | } else {
104 | switch loadingContent.type {
105 | case .onPage:
106 | loadingContent
107 | case .overlay:
108 | ZStack {
109 | content
110 | loadingContent
111 | }
112 | }
113 | }
114 | }.onChange(of: pageState) { newValue in
115 | sheetTrigger = (newValue == .failure)
116 | }.sheet(isPresented: $sheetTrigger) {
117 | errorContent
118 | }
119 | }
120 |
121 | #if os(iOS)
122 | @ViewBuilder
123 | private func fullscreenState(content: Content) -> some View {
124 | Group {
125 | if pageState == .successful {
126 | content
127 | } else {
128 | switch loadingContent.type {
129 | case .onPage:
130 | loadingContent
131 | case .overlay:
132 | ZStack {
133 | content
134 | loadingContent
135 | }
136 | }
137 | }
138 | }.onChange(of: pageState) { newValue in
139 | sheetTrigger = (newValue == .failure)
140 | }.fullScreenCover(isPresented: $sheetTrigger) {
141 | errorContent
142 | }
143 | }
144 | #endif
145 | }
146 |
147 | #if DEBUG
148 | @available(iOS 15.0, *)
149 | struct TestView: View {
150 | @State private var pageState: PageStates = .loading
151 |
152 | var body: some View {
153 | NavigationView {
154 | ScrollView {
155 | ForEach(0..<100, id: \.self) { _ in
156 | AsyncImage(url: URL(string: "https://picsum.photos/1000")) { phase in
157 | if let image = phase.image {
158 | image
159 | .resizable()
160 | .scaledToFill()
161 | } else {
162 | Color.gray
163 | }
164 | }.frame(height: 200, alignment: .center)
165 | .clipped()
166 | }
167 | }.navigationTitle("Example Content")
168 | }
169 | .akErrorView(pageState: $pageState) {
170 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
171 | pageState = .successful
172 | }
173 | }
174 | .onAppear {
175 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
176 | pageState = .failure
177 | }
178 | }
179 | }
180 | }
181 |
182 | #Preview {
183 | if #available(iOS 15.0, *) {
184 | TestView()
185 | } else {
186 | EmptyView()
187 | }
188 | }
189 | #endif
190 |
--------------------------------------------------------------------------------
/Sources/ErrorableView/Enums/ErrorPresentTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ErrorPresentTypes.swift
3 | //
4 | //
5 | // Created by Mehmet Ateş on 24.11.2023.
6 | //
7 |
8 | @frozen public enum ErrorPresentTypes {
9 | case onPage
10 | case fullScreen
11 | case sheet
12 | }
13 |
14 | @frozen public enum LoadingPresenterTypes {
15 | case onPage
16 | case overlay
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/ErrorableView/Enums/PageStates.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageStates.swift
3 | //
4 | //
5 | // Created by Mehmet Ateş on 24.11.2023.
6 | //
7 |
8 | @frozen public enum PageStates {
9 | case loading
10 | case successful
11 | case failure
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/ErrorableView/Views/DefaultErrorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultErrorView.swift
3 | //
4 | //
5 | // Created by Mehmet Ateş on 24.11.2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public protocol ErrorableView: View {
11 | var type: ErrorPresentTypes { get set }
12 | }
13 |
14 | @frozen public struct DefaultErrorView: ErrorableView {
15 | @Environment(\.dismiss) private var dismiss
16 | @Binding private var state: PageStates
17 | private var uimodel: DefaultErrorPageUIModel
18 | private var buttonAction: (() -> Void)?
19 | public var type: ErrorPresentTypes
20 |
21 | public init(
22 | uimodel: DefaultErrorPageUIModel = .Builder().build(),
23 | type: ErrorPresentTypes = .sheet,
24 | state: Binding,
25 | buttonAction: (() -> Void)? = nil
26 | ) {
27 | self.uimodel = uimodel
28 | self.type = type
29 | self.buttonAction = buttonAction
30 | self._state = state
31 | }
32 |
33 | public var body: some View {
34 | VStack {
35 | closeButtonView
36 | Spacer()
37 |
38 | VStack {
39 | iconImageView
40 | .imageScale(.large)
41 | .font(.largeTitle)
42 |
43 | Text(uimodel.title)
44 | .font(.title)
45 | .fontWeight(.bold)
46 | .multilineTextAlignment(.center)
47 | }
48 | .padding(.bottom)
49 |
50 | subTitleView
51 |
52 | Spacer()
53 |
54 | buttonView
55 | }.padding(.vertical)
56 | }
57 | }
58 |
59 | // MARK: - UIComponents
60 | private extension DefaultErrorView {
61 | @ViewBuilder
62 | var closeButtonView: some View {
63 | if type != .onPage {
64 | HStack {
65 | Spacer()
66 | Button {
67 | buttonAction?()
68 | state = .loading
69 | dismiss()
70 | } label: {
71 | Image(systemName: "xmark.circle.fill")
72 | .font(.title)
73 | }.accentColor(.secondary)
74 | }.padding(.horizontal)
75 | }
76 | }
77 |
78 | @ViewBuilder
79 | var iconImageView: some View {
80 | if let icon = uimodel.icon {
81 | Image(icon)
82 | } else if let systemName = uimodel.systemName {
83 | Image(systemName: systemName)
84 | }
85 | }
86 |
87 | @ViewBuilder
88 | var subTitleView: some View {
89 | if let subtitle = uimodel.subtitle {
90 | Group {
91 | if #available(iOS 15.0, *) {
92 | Text(subtitle)
93 | .foregroundStyle(.secondary)
94 | } else {
95 | Text(subtitle)
96 | .foregroundColor(.secondary)
97 | }
98 | }.font(.headline)
99 | .multilineTextAlignment(.center)
100 | }
101 | }
102 |
103 | @ViewBuilder
104 | var buttonView: some View {
105 | if let buttonTitle = uimodel.buttonTitle {
106 | if #available(iOS 15.0, *) {
107 | Button {
108 | buttonAction?()
109 | state = .loading
110 | dismiss()
111 | } label: {
112 | Spacer()
113 | Text(buttonTitle)
114 | .bold()
115 | .padding(.vertical, 5)
116 | Spacer()
117 | }.buttonStyle(.borderedProminent)
118 | .padding(.horizontal)
119 | } else {
120 | Button {
121 | buttonAction?()
122 | } label: {
123 | Spacer()
124 | Text(buttonTitle)
125 | .bold()
126 | Spacer()
127 | }.modifier(ErrorStateButtonModifier())
128 | .padding(.horizontal)
129 | }
130 | }
131 | }
132 | }
133 |
134 | // MARK: - UIModel
135 | @frozen public struct DefaultErrorPageUIModel {
136 | var title: LocalizedStringKey
137 | var subtitle: LocalizedStringKey?
138 | var icon: String?
139 | var systemName: String?
140 | var buttonTitle: LocalizedStringKey?
141 |
142 | public class Builder {
143 | private var type: ErrorPresentTypes = .sheet
144 | private var title: LocalizedStringKey = "Error!"
145 | private var subtitle: LocalizedStringKey? = "We encountered an error.\n Please try again later!"
146 | private var icon: String?
147 | private var systemName: String? = "externaldrive.fill.trianglebadge.exclamationmark"
148 | private var buttonTitle: LocalizedStringKey? = "Try Again!"
149 |
150 | public init() {}
151 |
152 | @discardableResult
153 | public func type(_ type: ErrorPresentTypes) -> Self {
154 | self.title = title
155 | return self
156 | }
157 |
158 | @discardableResult
159 | public func title(_ title: LocalizedStringKey) -> Self {
160 | self.title = title
161 | return self
162 | }
163 |
164 | @discardableResult
165 | public func subtitle(_ subtitle: LocalizedStringKey?) -> Self {
166 | self.subtitle = subtitle
167 | return self
168 | }
169 |
170 | @discardableResult
171 | public func icon(_ icon: String?) -> Self {
172 | self.icon = icon
173 | return self
174 | }
175 |
176 | @discardableResult
177 | public func systemName(_ systemName: String?) -> Self {
178 | self.systemName = systemName
179 | return self
180 | }
181 |
182 | @discardableResult
183 | public func buttonTitle(_ buttonTitle: LocalizedStringKey?) -> Self {
184 | self.buttonTitle = buttonTitle
185 | return self
186 | }
187 |
188 | public func build() -> DefaultErrorPageUIModel {
189 | DefaultErrorPageUIModel(
190 | title: title,
191 | subtitle: subtitle,
192 | icon: icon,
193 | systemName: systemName,
194 | buttonTitle: buttonTitle
195 | )
196 | }
197 | }
198 | }
199 |
200 | // MARK: - ViewModifer(s)
201 | @frozen public struct ErrorStateButtonModifier: ViewModifier {
202 | public func body(content: Content) -> some View {
203 | content
204 | .padding(.vertical, 5)
205 | .foregroundColor(.primary)
206 | .background(Color.accentColor)
207 | .clipShape(RoundedRectangle(cornerRadius: 8))
208 | }
209 | }
210 |
211 | #Preview {
212 | DefaultErrorView(state: .constant(.loading))
213 | }
214 |
--------------------------------------------------------------------------------
/Sources/ErrorableView/Views/DefaultLoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultLoadingView.swift
3 | //
4 | //
5 | // Created by Mehmet Ateş on 24.11.2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public protocol LoadingView: View {
11 | var type: LoadingPresenterTypes { get set }
12 | }
13 |
14 | @frozen public struct DefaultLoadingView: LoadingView {
15 | private let loadingText: LocalizedStringKey
16 | private let progressViewColor: Color
17 | public var type: LoadingPresenterTypes
18 |
19 | public init(
20 | loadingText: LocalizedStringKey,
21 | progressViewColor: Color = .accentColor,
22 | type: LoadingPresenterTypes = .onPage
23 | ) {
24 | self.loadingText = loadingText
25 | self.progressViewColor = progressViewColor
26 | self.type = type
27 | }
28 |
29 | public var body: some View {
30 | #if os(macOS)
31 | ZStack {
32 | Rectangle()
33 | .opacity(type == .onPage ? 1 : 0.3)
34 | VStack {
35 | if #available(iOS 15.0, *) {
36 | ProgressView()
37 | .scaleEffect(1.2)
38 | .tint(progressViewColor)
39 | } else {
40 | ProgressView()
41 | .scaleEffect(1.2)
42 | }
43 | Text(loadingText)
44 | .font(.caption)
45 | .foregroundColor(.secondary)
46 | .padding(.top)
47 | }
48 | }.ignoresSafeArea()
49 | #else
50 | switch type {
51 | case .onPage:
52 | VStack {
53 | ProgressView()
54 | .scaleEffect(1.2)
55 | .tint(progressViewColor)
56 |
57 | Text(loadingText)
58 | .foregroundColor(.secondary)
59 | .padding(.top)
60 | }
61 | case .overlay:
62 | VStack {
63 | HStack {
64 | Spacer()
65 | }
66 | Spacer()
67 | ProgressView()
68 | .scaleEffect(1.2)
69 | .tint(progressViewColor)
70 |
71 | Text(loadingText)
72 | .foregroundColor(.secondary)
73 | .padding(.top)
74 | Spacer()
75 | }.background {
76 | Rectangle()
77 | .foregroundStyle(.ultraThinMaterial)
78 | }.ignoresSafeArea()
79 | }
80 | #endif
81 | }
82 | }
83 |
84 | #Preview {
85 | DefaultLoadingView(loadingText: "Loading...")
86 | }
87 |
--------------------------------------------------------------------------------
/Tests/ErrorableViewTests/ErrorableViewTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import ErrorableView
3 |
4 | final class ErrorableViewTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------