From 7d3b81ce37f584ef8e49c3cc02d22376b1a54336 Mon Sep 17 00:00:00 2001 From: KC-2001MS Date: Sun, 29 Jun 2025 14:22:04 +0900 Subject: [PATCH 1/5] Add docc and initializer --- .../Processing/AppVersionManager.swift | 5 +- .../OnboardingUI/Processing/Onboarding.swift | 89 ++++++++++++++++++- .../UI/Modifier/ColorButtonStyle.swift | 3 +- .../UI/Modifier/OnboardingSheet.swift | 8 +- .../Modifier/OnboardingTextFormatting.swift | 4 +- .../UI/View/OnboardingCardView.swift | 9 +- .../UI/View/OnboardingSheetView.swift | 21 +++-- .../UI/View/Parts/OnboardingButton.swift | 3 +- .../UI/View/Parts/OnboardingContent.swift | 3 +- .../UI/View/Parts/OnboardingItem.swift | 1 + .../UI/View/Parts/OnboardingSubtitle.swift | 4 +- .../UI/View/Parts/OnboardingTitle.swift | 3 +- 12 files changed, 129 insertions(+), 24 deletions(-) diff --git a/Sources/OnboardingUI/Processing/AppVersionManager.swift b/Sources/OnboardingUI/Processing/AppVersionManager.swift index af48f8857..80f1a0009 100644 --- a/Sources/OnboardingUI/Processing/AppVersionManager.swift +++ b/Sources/OnboardingUI/Processing/AppVersionManager.swift @@ -59,19 +59,21 @@ public class AppVersionManager { } } /// Default initializer + /// Creates a new AppVersionManager instance. public init() { self.version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String self.lastOpenedVersion = userDefaults.string(forKey: "LastOpenedVersion") ?? "" } } /// AppVersionManager environment key +/// The environment key for the AppVersionManager. @available(iOS 17.0,macOS 14.0,watchOS 10.0,tvOS 17.0,visionOS 1.0,*) public struct AppVersionManagerKey: EnvironmentKey { public static var defaultValue = AppVersionManager() } /// AppVersionManager environment values -@available(iOS 17.0,macOS 14.0,watchOS 10.0,tvOS 17.0,visionOS 1.0,*) public extension EnvironmentValues { + /// Accessor for the AppVersionManager value in EnvironmentValues. var appVersionManager: AppVersionManager { get { self[AppVersionManagerKey.self] } set { self[AppVersionManagerKey.self] = newValue } @@ -91,3 +93,4 @@ func filled(_ target: [Int], count: Int) -> [Int] { (i < target.count) ? target[i] : 0 } } + diff --git a/Sources/OnboardingUI/Processing/Onboarding.swift b/Sources/OnboardingUI/Processing/Onboarding.swift index 5ac5921bd..18d58430b 100644 --- a/Sources/OnboardingUI/Processing/Onboarding.swift +++ b/Sources/OnboardingUI/Processing/Onboarding.swift @@ -9,13 +9,16 @@ import Foundation import SwiftUI @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +/// A protocol that defines onboarding content for an app. public protocol Onboarding: Identifiable, Sendable { + /// The unique identifier for this onboarding item. var id: UUID { get } /// Onboarding Title var title: Text { get } /// Variables indicating features @FeatureBuilder var features: Array { get } + /// Optional link displayed on the onboarding screen. var link: Link? { get } } @@ -31,7 +34,9 @@ public extension Onboarding { } /// Structures to build features to display onboarding @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +/// A structure representing a single onboarding feature. public struct Feature: Identifiable, Sendable { + /// The unique identifier for this feature. public var id: UUID = UUID() /// Onboarding Item Title public var title: Text = Text(verbatim: "") @@ -47,6 +52,11 @@ public struct Feature: Identifiable, Sendable { self.message = nil } + /// Initializes a feature with title, image and message. + /// - Parameters: + /// - title: The title text of the feature. + /// - image: The image representing the feature. + /// - message: The message text of the feature. public init(title: Text,image: Image?,message: Text?) { self.id = UUID() self.title = title @@ -54,6 +64,11 @@ public struct Feature: Identifiable, Sendable { self.message = message } + /// Initializes a feature using closures to create title, image and message. + /// - Parameters: + /// - title: Closure returning the title text. + /// - image: Closure returning the image. + /// - message: Closure returning the message text. public init(title: () -> Text,image: () -> Image?,message: () -> Text?) { self.id = UUID() self.title = title() @@ -71,22 +86,61 @@ public struct Feature: Identifiable, Sendable { self.image = Image(systemName: imageName) self.message = Text(message) } + + /// Initializes a feature with localized string resource message. + /// - Parameters: + /// - title: Title outlining the features + /// - imageName: Images showing features + /// - message: Description of features as a localized resource + public init(_ title: LocalizedStringKey,imageName: String, message: LocalizedStringResource) { + self.id = UUID() + self.title = Text(title) + self.image = Image(systemName: imageName) + self.message = Text(message) + } + /// Initializes a feature with localized string resource message. + /// - Parameters: + /// - title: Title outlining the features as a localized resource + /// - imageName: Images showing features + /// - message: Description of features as a localized resource + public init(_ title: LocalizedStringResource,imageName: String, message: LocalizedStringResource) { + self.id = UUID() + self.title = Text(title) + self.image = Image(systemName: imageName) + self.message = Text(message) + } /// General initializer /// - Parameters: /// - title: Title outlining the features /// - imageName: Images showing features /// - message: Description of features - @_disfavoredOverload public init(_ title: S,imageName: String, message: S) where S : StringProtocol { + @_disfavoredOverload + /// Initializes a feature with string titles and messages. + /// - Parameters: + /// - title: String title of the feature. + /// - imageName: System image name. + /// - message: Description message string. + public init(_ title: S,imageName: String, message: S) where S : StringProtocol { self.id = UUID() self.title = Text(title) self.image = Image(systemName: imageName) self.message = Text(message) } + /// Returns a new feature with the title replaced. + /// - Parameter string: The new title string. + /// - Returns: A new Feature instance with updated title. public func title(_ string: String) -> Self { .init(title: Text(string), image: self.image, message: self.message) } + /// Returns a new feature with the localized title replaced. + /// - Parameters: + /// - key: The localized string key for the new title. + /// - tableName: The table name for localization. + /// - bundle: The bundle containing the localized strings. + /// - comment: Contextual comment for translators. + /// - Returns: A new Feature instance with updated title. public func title( _ key: LocalizedStringKey, tableName: String? = nil, @@ -96,14 +150,27 @@ public struct Feature: Identifiable, Sendable { .init(title: Text(key, tableName: tableName, bundle: bundle, comment: comment), image: self.image, message: self.message) } + /// Returns a new feature with the image replaced by a system image. + /// - Parameter systemName: The system image name. + /// - Returns: A new Feature instance with updated image. public func image(systemName: String) -> Self { .init(title: self.title, image: Image(systemName: systemName), message: self.message) } + /// Returns a new feature with the message replaced. + /// - Parameter string: The new message string. + /// - Returns: A new Feature instance with updated message. public func message(_ string: String) -> Self { .init(title: self.title, image: self.image, message: Text(string)) } + /// Returns a new feature with the localized message replaced. + /// - Parameters: + /// - key: The localized string key for the new message. + /// - tableName: The table name for localization. + /// - bundle: The bundle containing the localized strings. + /// - comment: Contextual comment for translators. + /// - Returns: A new Feature instance with updated message. public func message( _ key: LocalizedStringKey, tableName: String? = nil, @@ -115,30 +182,46 @@ public struct Feature: Identifiable, Sendable { } /// Result builder that allows you to freely build Feature structures @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +/// A result builder to construct onboarding features. @resultBuilder public struct FeatureBuilder { /// Required in resultBuilder + /// Combines multiple Features into an array. + /// - Parameter parts: A variadic list of Features. + /// - Returns: An array of Features. public static func buildBlock(_ parts: Feature...) -> Array { parts } /// Enable if + /// Handles optional Feature arrays. + /// - Parameter parts: An optional array of Features. + /// - Returns: The first Feature or a default Feature. public static func buildOptional(_ parts: [Feature]?) -> Feature { parts?.first ?? Feature() } - /// Enable if-else + /// Enable if-else (first branch) + /// - Parameter parts: An array of Features. + /// - Returns: The first Feature or a default Feature. public static func buildEither(first parts: [Feature]) -> Feature { parts.first ?? Feature() } - /// Enable if-else + /// Enable if-else (second branch) + /// - Parameter parts: An array of Features. + /// - Returns: The first Feature or a default Feature. public static func buildEither(second parts: [Feature]) -> Feature { parts.first ?? Feature() } /// Enable for-in + /// - Parameter parts: An array of Feature arrays. + /// - Returns: The first Feature from the first array or a default Feature. public static func buildArray(_ parts: [[Feature]]) -> Feature { parts.first?.first ?? Feature() } /// Enable #if + /// - Parameter parts: An array of Features. + /// - Returns: The array of Features. public static func buildLimitedAvailability(_ parts: [Feature]) -> Array { parts } } + diff --git a/Sources/OnboardingUI/UI/Modifier/ColorButtonStyle.swift b/Sources/OnboardingUI/UI/Modifier/ColorButtonStyle.swift index 775a5285f..06954f84b 100644 --- a/Sources/OnboardingUI/UI/Modifier/ColorButtonStyle.swift +++ b/Sources/OnboardingUI/UI/Modifier/ColorButtonStyle.swift @@ -8,7 +8,8 @@ import SwiftUI /// Modifier to build the look and feel of buttons that continue to be used in onboarding -@available(iOS 17.0,macOS 14.0,visionOS 1.0,*) +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct ColorButtonStyle: ButtonStyle { /// Foreground Color var foregroundColor: Color = .white diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift b/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift index 3987b2647..e5271c3a0 100644 --- a/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift +++ b/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift @@ -8,6 +8,7 @@ import SwiftUI @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) struct OnboardingSheet: ViewModifier { @Binding public var isPresented: Bool @@ -45,6 +46,7 @@ struct OnboardingSheet: ViewModifier { } @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public extension View { ///Modifier to display sheets based on Onboarding protocol compliant structures func onboardingSheet(isPresented: Binding,_ onboarding: Content) -> some View where Content : Onboarding { @@ -53,9 +55,9 @@ public extension View { } #Preview { - @State var isPresented: Bool = true + @Previewable @State var isPresented: Bool = true - return Group { + Button(String("Open Onboarding")) { isPresented = true } @@ -63,5 +65,5 @@ public extension View { .frame(width: 400, height: 300) #endif .onboardingSheet(isPresented: $isPresented, PreviewWhatIsNewOnboarding()) - } + } diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingTextFormatting.swift b/Sources/OnboardingUI/UI/Modifier/OnboardingTextFormatting.swift index ff18199fb..2df875340 100644 --- a/Sources/OnboardingUI/UI/Modifier/OnboardingTextFormatting.swift +++ b/Sources/OnboardingUI/UI/Modifier/OnboardingTextFormatting.swift @@ -7,8 +7,9 @@ import SwiftUI -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) /// The three items that comprise onboarding +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public enum OnboardingTextFormattingStyle { case title case subtitle @@ -16,6 +17,7 @@ public enum OnboardingTextFormattingStyle { } @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public extension Text { /// Modifier to change the style to suit it. func onboardingTextFormatting(style: OnboardingTextFormattingStyle) -> some View { diff --git a/Sources/OnboardingUI/UI/View/OnboardingCardView.swift b/Sources/OnboardingUI/UI/View/OnboardingCardView.swift index 28e8b062f..836e13d48 100644 --- a/Sources/OnboardingUI/UI/View/OnboardingCardView.swift +++ b/Sources/OnboardingUI/UI/View/OnboardingCardView.swift @@ -7,7 +7,8 @@ import SwiftUI -@available(iOS 17.0, macOS 14.0, tvOS 17.0, visionOS 1.0, *) +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct OnboardingCardView: View { @Binding var isPresented: Bool var title: V1 @@ -44,7 +45,7 @@ public struct OnboardingCardView: View { } }) } - + /// View public var body: some View { if isPresented { VStack { @@ -89,9 +90,9 @@ public struct OnboardingCardView: View { } #Preview { - @State var isPresented = true + @Previewable @State var isPresented = true - return ScrollView { + ScrollView { OnboardingCardView(isPresented: $isPresented, onboarding: PreviewWhatIsNewOnboarding()) .padding() } diff --git a/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift b/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift index a64ab4302..456f25338 100644 --- a/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift +++ b/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift @@ -7,22 +7,25 @@ import SwiftUI -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) /// View to show onboarding in sheets +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct OnboardingSheetView: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize - /// Title View + /// The view used for the title area. var title: V1 - /// View displaying features + /// The view displaying onboarding features. var content: V2 + /// The link view shown in the sheet. var link: V3 - /// Button view at the bottom + /// The button view at the bottom of the onboarding sheet. var button: V4 - /// Initializer for the three Views that make up the OnboardingSheet + /// Creates an onboarding sheet view with custom content, title, link, and button views. /// - Parameters: /// - title: Title View /// - content: View displaying features + /// - link: Link view in the sheet /// - button: Button view at the bottom public init( @ViewBuilder title: () -> V1, @@ -36,6 +39,12 @@ public struct OnboardingSheetView: View { self.button = button() } + /// Creates an onboarding sheet view with a default link view. + /// - Parameters: + /// - title: Title View + /// - content: View displaying features + /// - link: Link view in the sheet (default is EmptyView) + /// - button: Button view at the bottom public init( @ViewBuilder title: () -> V1, @ViewBuilder content: () -> V2, @@ -47,7 +56,7 @@ public struct OnboardingSheetView: View { self.link = link self.button = button() } - /// View + /// The view body providing the onboarding UI layout. public var body: some View { GeometryReader { geom in VStack(alignment: .center) { diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingButton.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingButton.swift index 911a08bbe..824908ea1 100644 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingButton.swift +++ b/Sources/OnboardingUI/UI/View/Parts/OnboardingButton.swift @@ -7,8 +7,9 @@ import SwiftUI -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) /// Continue button +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct ContinueButton: View { /// Foreground Color let foregroundColor: Color diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift index 6a8c3d986..6af27a6fe 100644 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift +++ b/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift @@ -7,8 +7,9 @@ import SwiftUI -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) /// Content to be used for onboarding +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct OnboardingContent: View { /// Text var TextView: Text diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingItem.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingItem.swift index f4d2b5b6b..7d780b4e9 100644 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingItem.swift +++ b/Sources/OnboardingUI/UI/View/Parts/OnboardingItem.swift @@ -22,6 +22,7 @@ import SwiftUI /// - Content: The type of the content view. /// - S: The type of the shape style. @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct OnboardingItem: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize /// The content view of the onboarding item. diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift index c6b400256..7e2247aea 100644 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift +++ b/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift @@ -7,9 +7,9 @@ import SwiftUI -//項目タイトルView(完成) -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) /// Subtitle to be used for onboarding +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct OnboardingSubtitle: View { /// Text var TextView: Text diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift index 6baba92f5..acc6dc5cf 100644 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift +++ b/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift @@ -7,8 +7,9 @@ import SwiftUI -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) /// Title to be used for onboarding +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) public struct OnboardingTitle: View { /// Text var TextView: Text From d326983997ae29375cd67e0ab6bac0bb81cd5532 Mon Sep 17 00:00:00 2001 From: KC-2001MS Date: Sun, 29 Jun 2025 14:34:24 +0900 Subject: [PATCH 2/5] fix init --- .../OnboardingUI/Processing/Onboarding.swift | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Sources/OnboardingUI/Processing/Onboarding.swift b/Sources/OnboardingUI/Processing/Onboarding.swift index 18d58430b..0daeb16a9 100644 --- a/Sources/OnboardingUI/Processing/Onboarding.swift +++ b/Sources/OnboardingUI/Processing/Onboarding.swift @@ -92,34 +92,29 @@ public struct Feature: Identifiable, Sendable { /// - title: Title outlining the features /// - imageName: Images showing features /// - message: Description of features as a localized resource - public init(_ title: LocalizedStringKey,imageName: String, message: LocalizedStringResource) { + public init(_ title: LocalizedStringKey,imageName: String, messageResource: LocalizedStringResource) { self.id = UUID() self.title = Text(title) self.image = Image(systemName: imageName) - self.message = Text(message) + self.message = Text(messageResource) } /// Initializes a feature with localized string resource message. /// - Parameters: - /// - title: Title outlining the features as a localized resource + /// - titleResource: Title outlining the features as a localized resource /// - imageName: Images showing features - /// - message: Description of features as a localized resource - public init(_ title: LocalizedStringResource,imageName: String, message: LocalizedStringResource) { + /// - messageResource: Description of features as a localized resource + public init(_ titleResource: LocalizedStringResource,imageName: String, messageResource: LocalizedStringResource) { self.id = UUID() - self.title = Text(title) + self.title = Text(titleResource) self.image = Image(systemName: imageName) - self.message = Text(message) + self.message = Text(messageResource) } - /// General initializer - /// - Parameters: - /// - title: Title outlining the features - /// - imageName: Images showing features - /// - message: Description of features - @_disfavoredOverload /// Initializes a feature with string titles and messages. /// - Parameters: /// - title: String title of the feature. /// - imageName: System image name. /// - message: Description message string. + @_disfavoredOverload public init(_ title: S,imageName: String, message: S) where S : StringProtocol { self.id = UUID() self.title = Text(title) From bc65e07a745ead4e6815fe86220d7523bfeb3213 Mon Sep 17 00:00:00 2001 From: KC-2001MS Date: Sun, 29 Jun 2025 14:38:10 +0900 Subject: [PATCH 3/5] init fix --- Sources/OnboardingUI/Processing/Onboarding.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OnboardingUI/Processing/Onboarding.swift b/Sources/OnboardingUI/Processing/Onboarding.swift index 0daeb16a9..67420414e 100644 --- a/Sources/OnboardingUI/Processing/Onboarding.swift +++ b/Sources/OnboardingUI/Processing/Onboarding.swift @@ -103,7 +103,7 @@ public struct Feature: Identifiable, Sendable { /// - titleResource: Title outlining the features as a localized resource /// - imageName: Images showing features /// - messageResource: Description of features as a localized resource - public init(_ titleResource: LocalizedStringResource,imageName: String, messageResource: LocalizedStringResource) { + public init(titleResource: LocalizedStringResource,imageName: String, messageResource: LocalizedStringResource) { self.id = UUID() self.title = Text(titleResource) self.image = Image(systemName: imageName) From 3688c3c58aa22eae4d271e62e170c31c19abf31e Mon Sep 17 00:00:00 2001 From: KC-2001MS Date: Thu, 17 Jul 2025 17:26:52 +0900 Subject: [PATCH 4/5] Undergoing changes for the next operating system --- .../UI/NewFeatureOnboardingSheetView.swift | 21 +- .../UI/WelcomeOnboardingSheetView.swift | 21 +- .../UI/Environment/EnvironmentValues.swift | 14 ++ .../UI/Modifier/OnboardingSheet.swift | 65 +++--- .../UI/Modifier/OnboardingViewStyle.swift | 17 ++ .../ButtonStyle}/ColorButtonStyle.swift | 0 .../UI/Style/OnboardingViewStyle.swift | 188 ++++++++++++++++++ .../AutomaticOnboardingViewStyle.swift | 35 ++++ .../BasicOnboardingViewStyle.swift | 135 +++++++++++++ .../ClassicOnboardingViewStyle.swift | 150 ++++++++++++++ .../ColoredGlassOnboardingViewStyle.swift | 135 +++++++++++++ .../GlassOnboardingViewStyle.swift | 135 +++++++++++++ .../UI/View/OnboardingCardView.swift | 6 +- .../UI/View/OnboardingSheetView.swift | 24 ++- .../OnboardingUI/UI/View/OnboardingView.swift | 47 +++++ .../UI/View/Parts/OnboardingContent.swift | 68 ------- .../UI/View/Parts/OnboardingSubtitle.swift | 68 ------- .../UI/View/Parts/OnboardingTitle.swift | 69 ------- 18 files changed, 945 insertions(+), 253 deletions(-) create mode 100644 Sources/OnboardingUI/UI/Environment/EnvironmentValues.swift create mode 100644 Sources/OnboardingUI/UI/Modifier/OnboardingViewStyle.swift rename Sources/OnboardingUI/UI/{Modifier => Style/ButtonStyle}/ColorButtonStyle.swift (100%) create mode 100644 Sources/OnboardingUI/UI/Style/OnboardingViewStyle.swift create mode 100644 Sources/OnboardingUI/UI/Style/OnboardingViewStyle/AutomaticOnboardingViewStyle.swift create mode 100644 Sources/OnboardingUI/UI/Style/OnboardingViewStyle/BasicOnboardingViewStyle.swift create mode 100644 Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ClassicOnboardingViewStyle.swift create mode 100644 Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ColoredGlassOnboardingViewStyle.swift create mode 100644 Sources/OnboardingUI/UI/Style/OnboardingViewStyle/GlassOnboardingViewStyle.swift create mode 100644 Sources/OnboardingUI/UI/View/OnboardingView.swift delete mode 100644 Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift delete mode 100644 Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift delete mode 100644 Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift diff --git a/.Sample/Sample/UI/NewFeatureOnboardingSheetView.swift b/.Sample/Sample/UI/NewFeatureOnboardingSheetView.swift index 2c1191f69..8b8fd31dc 100644 --- a/.Sample/Sample/UI/NewFeatureOnboardingSheetView.swift +++ b/.Sample/Sample/UI/NewFeatureOnboardingSheetView.swift @@ -17,21 +17,28 @@ struct NewFeatureOnboardingSheetView: View { var body: some View { OnboardingSheetView { - OnboardingTitle("What's New in\nOnboardingUI") + Text("What's New in\nOnboardingUI") + .onboardingTextFormatting(style: .title) } content: { OnboardingItem(systemName: "wrench.and.screwdriver",shape: .red) { - OnboardingSubtitle("New AppVersionManager environment variable") - OnboardingContent("The new AppVersionManager environment variable allows you to display onboarding at the intended time.") + Text("New AppVersionManager environment variable") + .onboardingTextFormatting(style: .subtitle) + Text("The new AppVersionManager environment variable allows you to display onboarding at the intended time.") + .onboardingTextFormatting(style: .content) } OnboardingItem(systemName: "building.columns",shape: .blue) { - OnboardingSubtitle("New Onboarding protocol and Feature structure") - OnboardingContent("The new Onboarding protocol and Feature structure make it easier to create onboarding. There is no need to build views.") + Text("New Onboarding protocol and Feature structure") + .onboardingTextFormatting(style: .subtitle) + Text("The new Onboarding protocol and Feature structure make it easier to create onboarding. There is no need to build views.") + .onboardingTextFormatting(style: .content) } OnboardingItem(systemName: "wrench.and.screwdriver",shape: .orange) { - OnboardingSubtitle("Customize the look and feel") - OnboardingContent("Of course, it is also customizable. You can build onboarding at will.") + Text("Customize the look and feel") + .onboardingTextFormatting(style: .subtitle) + Text("Of course, it is also customizable. You can build onboarding at will.") + .onboardingTextFormatting(style: .content) } } button: { ContinueButton(color: .accentColor, action: action) diff --git a/.Sample/Sample/UI/WelcomeOnboardingSheetView.swift b/.Sample/Sample/UI/WelcomeOnboardingSheetView.swift index f5f79fd24..43615b2b6 100644 --- a/.Sample/Sample/UI/WelcomeOnboardingSheetView.swift +++ b/.Sample/Sample/UI/WelcomeOnboardingSheetView.swift @@ -18,21 +18,28 @@ struct WelcomeOnboardingSheetView: View { var body: some View { OnboardingSheetView { - OnboardingTitle("Welcome to\nOnboardingUI") + Text("Welcome to\nOnboardingUI") + .onboardingTextFormatting(style: .title) } content: { OnboardingItem(systemName: "applescript",shape: .red) { - OnboardingSubtitle("Easy to Make") - OnboardingContent("Onboarding screens like Apple's stock apps can be easily created with SwiftUI.") + Text("Easy to Make") + .onboardingTextFormatting(style: .subtitle) + Text("Onboarding screens like Apple's stock apps can be easily created with SwiftUI.") + .onboardingTextFormatting(style: .content) } OnboardingItem(systemName: "apple.logo") { - OnboardingSubtitle("Not only for iPhone, but also for Mac, iPad, Vision Pro") - OnboardingContent("It supports not only iPhone, but also Mac, iPad, and Vision Pro. Therefore, there is no need to rewrite the code for each device.") + Text("Not only for iPhone, but also for Mac, iPad, Vision Pro") + .onboardingTextFormatting(style: .subtitle) + Text("It supports not only iPhone, but also Mac, iPad, and Vision Pro. Therefore, there is no need to rewrite the code for each device.") + .onboardingTextFormatting(style: .content) } OnboardingItem(systemName: "circle.badge.checkmark",mode: .palette,primary: .primary,secondary: .blue) { - OnboardingSubtitle("Customize SF Symbols") - OnboardingContent("When using a highly customizable implementation method, multi-color and SF symbol hierarchies are supported and can be freely customized.") + Text("Customize SF Symbols") + .onboardingTextFormatting(style: .subtitle) + Text("When using a highly customizable implementation method, multi-color and SF symbol hierarchies are supported and can be freely customized.") + .onboardingTextFormatting(style: .content) } } button: { ContinueButton(color: .accentColor, action: action) diff --git a/Sources/OnboardingUI/UI/Environment/EnvironmentValues.swift b/Sources/OnboardingUI/UI/Environment/EnvironmentValues.swift new file mode 100644 index 000000000..5c1c2ec6f --- /dev/null +++ b/Sources/OnboardingUI/UI/Environment/EnvironmentValues.swift @@ -0,0 +1,14 @@ +// +// EnvironmentValues.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/07. +// + +import SwiftUI + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +extension EnvironmentValues { + @Entry internal var onboardingViewStyle: AnyOnboardingViewStyle = AnyOnboardingViewStyle(.automatic) +} diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift b/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift index e5271c3a0..b97a8ccbc 100644 --- a/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift +++ b/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift @@ -9,36 +9,47 @@ import SwiftUI @available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) @available(watchOS, unavailable) -struct OnboardingSheet: ViewModifier { +struct OnboardingSheet: ViewModifier { @Binding public var isPresented: Bool - public let onboarding: any Onboarding + public let onboarding: O + + init(isPresented: Binding, onboarding: O) { + self._isPresented = isPresented + self.onboarding = onboarding + } public func body(content: Content) -> some View { content .sheet(isPresented: $isPresented) { - OnboardingSheetView { - onboarding.title - .onboardingTextFormatting(style: .title) - } content: { - ForEach(onboarding.features) { feature in - if let image = feature.image { - OnboardingItem { - image - } content: { - if let message = feature.message { - OnboardingSubtitle(feature.title) - OnboardingContent(message) + if #available(iOS 18.0,macOS 15.0,tvOS 18.0,visionOS 2.0,*) { + OnboardingView(onboarding: onboarding) + } else { + OnboardingSheetView { + onboarding.title + .onboardingTextFormatting(style: .title) + } content: { + ForEach(onboarding.features) { feature in + if let image = feature.image { + OnboardingItem { + image + } content: { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } } + } - } - } - } link: { - onboarding.link - } button: { - ContinueButton { - isPresented.toggle() + } link: { + onboarding.link + } button: { + ContinueButton { + isPresented.toggle() + } } } } @@ -50,14 +61,20 @@ struct OnboardingSheet: ViewModifier { public extension View { ///Modifier to display sheets based on Onboarding protocol compliant structures func onboardingSheet(isPresented: Binding,_ onboarding: Content) -> some View where Content : Onboarding { - modifier(OnboardingUI.OnboardingSheet(isPresented: isPresented, onboarding: onboarding)) + modifier( + OnboardingUI + .OnboardingSheet( + isPresented: isPresented, + onboarding: onboarding + ) + ) } } #Preview { @Previewable @State var isPresented: Bool = true - + if #available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) { Button(String("Open Onboarding")) { isPresented = true } @@ -65,5 +82,7 @@ public extension View { .frame(width: 400, height: 300) #endif .onboardingSheet(isPresented: $isPresented, PreviewWhatIsNewOnboarding()) + .onboardingViewStyle(.glass) + } } diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Modifier/OnboardingViewStyle.swift new file mode 100644 index 000000000..c5db0637b --- /dev/null +++ b/Sources/OnboardingUI/UI/Modifier/OnboardingViewStyle.swift @@ -0,0 +1,17 @@ +// +// onboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/07. +// + +import SwiftUI + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +public extension View { + func onboardingViewStyle(_ style: some OnboardingViewStyle) -> some View { + self.environment(\.onboardingViewStyle, AnyOnboardingViewStyle(style)) + } +} + diff --git a/Sources/OnboardingUI/UI/Modifier/ColorButtonStyle.swift b/Sources/OnboardingUI/UI/Style/ButtonStyle/ColorButtonStyle.swift similarity index 100% rename from Sources/OnboardingUI/UI/Modifier/ColorButtonStyle.swift rename to Sources/OnboardingUI/UI/Style/ButtonStyle/ColorButtonStyle.swift diff --git a/Sources/OnboardingUI/UI/Style/OnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle.swift new file mode 100644 index 000000000..141c9eba2 --- /dev/null +++ b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle.swift @@ -0,0 +1,188 @@ +// +// OnboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/04. +// + +import SwiftUI + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +@preconcurrency public protocol OnboardingViewStyle { + typealias Configuration = OnboardingViewStyleConfiguration + + associatedtype Body: View + + @preconcurrency @ViewBuilder func makeBody(configuration: Configuration) -> Body +} + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +struct AnyOnboardingViewStyle: OnboardingViewStyle { + private var _makeBody: (Configuration) -> AnyView + + init(_ style: S) { + _makeBody = { configuration in + AnyView(style.makeBody(configuration: configuration)) + } + } + + func makeBody(configuration: Configuration) -> some View { + _makeBody(configuration) + } +} + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +public struct OnboardingViewStyleConfiguration: Sendable { + + var dynamicTypeSize: DynamicTypeSize + + var title: Title + + var content: Content + + var footer: Footer + + var dismissButton: DismissButton + + init( + dynamicTypeSize: DynamicTypeSize, + title: Title, + content: Content, + footer: Footer , + dismissButton: DismissButton + ) { + self.dynamicTypeSize = dynamicTypeSize + self.title = title + self.content = content + self.footer = footer + self.dismissButton = dismissButton + } + + struct Title: View, Sendable { + var text: Text + + init(_ title: Text) { + self.text = title + } + + var body: some View { + text + } + } + + @preconcurrency + public struct Content: View, Sendable { + private let items: [Feature] + + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + + public init(_ items: [Feature]) { + self.items = items + } + + public var body: some View { + ForEach(items) { feature in + if let image = feature.image { + Group { + if dynamicTypeSize <= .xxxLarge { + HStack(alignment: .top,spacing: 20) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle(Color.accentColor) + .frame(width: 37.5, height: 37.5) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 2.5) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } else { + VStack(alignment: .leading) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle(Color.accentColor) + .frame(width: 37.5, height: 37.5) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 5) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } + } +#if os(tvOS) + .focusable() +#endif + } + } + } + + /// 必ず明示的にクロージャで描画する + public func callAsFunction( + @ViewBuilder _ content: @escaping (Feature) -> Content + ) -> some View { + ForEach(items) { item in + content(item) + } + } + } + + @preconcurrency + public struct Footer: View, Sendable { + var content: AnyView + + init(@ViewBuilder content: () -> V) { + self.content = AnyView(content()) + } + + public var body: some View { + content + } + } + + @preconcurrency public struct DismissButton: View, Sendable { + @Environment(\.dismiss) var dismiss + + var text: Text + + public init(text: Text) { + self.text = text + } + + public var body: some View { + Button { + dismiss() + } label: { + text + } + } + + public func callAsFunction( + @ViewBuilder content: (Text) -> V + ) -> some View { + Button { + dismiss() + } label: { + content(text) + } + } + } +} + diff --git a/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/AutomaticOnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/AutomaticOnboardingViewStyle.swift new file mode 100644 index 000000000..4b52f7d20 --- /dev/null +++ b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/AutomaticOnboardingViewStyle.swift @@ -0,0 +1,35 @@ +// +// AutomaticOnboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/17. +// + +import SwiftUI + +@available(iOS 17, macOS 14, tvOS 17, visionOS 1, *) +@available(watchOS, unavailable) +public struct AutomaticOnboardingViewStyle: OnboardingViewStyle { + public func makeBody(configuration: Configuration) -> some View { + if #available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) { + ColoredGlassOnboardingViewStyle().makeBody(configuration: configuration) + } else { + ClassicOnboardingViewStyle().makeBody(configuration: configuration) + } + } +} + +@available(iOS 17, macOS 14, tvOS 17, visionOS 1, *) +@available(watchOS, unavailable) +extension OnboardingViewStyle where Self == AutomaticOnboardingViewStyle { + /// The regular About view style to use with an About view. + public static var automatic: AutomaticOnboardingViewStyle { + return AutomaticOnboardingViewStyle() + } +} + + +#Preview { + OnboardingView(onboarding: PreviewWhatIsNewOnboarding()) + .onboardingViewStyle(.automatic) +} diff --git a/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/BasicOnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/BasicOnboardingViewStyle.swift new file mode 100644 index 000000000..b8c606f85 --- /dev/null +++ b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/BasicOnboardingViewStyle.swift @@ -0,0 +1,135 @@ +// +// BasicOnboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/04. +// + +import SwiftUI + +@available(iOS 18, macOS 15, tvOS 18, visionOS 2, *) +@available(watchOS, unavailable) +public struct BasicOnboardingViewStyle: OnboardingViewStyle { + public func makeBody(configuration: Configuration) -> some View { + VStack(alignment: .center) { + ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 30) { + configuration.title + .font(.title2) + .fontWeight(.bold) + VStack(alignment: .leading, spacing: 20) { + configuration.content { feature in + if let image = feature.image { + Group { + if configuration.dynamicTypeSize <= .xxxLarge { + HStack(alignment: .top,spacing: 20) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle( + Color.accentColor + ) + .frame( + width: 37.5, + height: 37.5 + ) + .accessibilityHidden(true) + + VStack( + alignment: .leading, + spacing: 2.5 + ) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } else { + VStack(alignment: .leading) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle( + Color.accentColor + ) + .frame( + width: 37.5, + height: 37.5 + ) + .accessibilityHidden(true) + + VStack( + alignment: .leading, + spacing: 5 + ) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } + } +#if os(tvOS) + .focusable() +#endif + } + } + } + + if configuration.dynamicTypeSize > .xxxLarge { + configuration.footer + + configuration.dismissButton { text in + text + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.borderedProminent) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding([.horizontal, .bottom],40) + .padding(.top, 80) + } + + if configuration.dynamicTypeSize <= .xxxLarge { + VStack(alignment: .center, spacing: 25) { + configuration.footer + + configuration.dismissButton { text in + text + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.borderedProminent) + .padding(.horizontal, 40) + } + } + } + } +} + +@available(iOS 18, macOS 15, tvOS 18, visionOS 2, *) +@available(watchOS, unavailable) +extension OnboardingViewStyle where Self == BasicOnboardingViewStyle { + /// The regular About view style to use with an About view. + public static var basic: BasicOnboardingViewStyle { + return BasicOnboardingViewStyle() + } +} + + +#Preview { + if #available(iOS 18, macOS 15, tvOS 18, visionOS 2, *) { + OnboardingView(onboarding: PreviewWhatIsNewOnboarding()) + .onboardingViewStyle(.basic) + } +} diff --git a/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ClassicOnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ClassicOnboardingViewStyle.swift new file mode 100644 index 000000000..144fc2bf7 --- /dev/null +++ b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ClassicOnboardingViewStyle.swift @@ -0,0 +1,150 @@ +// +// ClassicOnboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/04. +// + +import SwiftUI + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +public struct ClassicOnboardingViewStyle: OnboardingViewStyle { + public func makeBody(configuration: Configuration) -> some View { + GeometryReader { geom in + VStack(alignment: .center) { + ScrollView(showsIndicators: false) { + VStack(alignment: .center,spacing: geom.size.height / 60) { + VStack(spacing: 0) { + + configuration.title +#if os(macOS) + .font( + .custom( + "", + size: CGFloat(40), + relativeTo: .largeTitle + ) + ) + .fontWeight(.regular) +#elseif os(visionOS) + .font(.extraLargeTitle2) + .fontWeight(.bold) +#else + .font(.largeTitle) + .fontWeight(.bold) +#endif + .multilineTextAlignment(.center) + .minimumScaleFactor(0.75) + .lineLimit(3) + .accessibilityLabel(configuration.title.text) + .padding(.vertical, geom.size.height / 20) + } + + VStack(alignment: .leading, spacing: 35) { + configuration.content { feature in + if let image = feature.image { + + OnboardingItem { + image + } content: { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + + } + } + } +#if os(iOS) + .frame(maxWidth: 440) +#elseif os(macOS) + .frame(maxWidth: 350) +#elseif os(visionOS) + .frame(maxWidth: 555) +#else + .frame(maxWidth: .infinity) +#endif + if configuration.dynamicTypeSize > .xxxLarge { + configuration.footer + + configuration.dismissButton + .buttonStyle(ColorButtonStyle()) +#if os(iOS) + .padding( + .bottom, + 70 - geom.size.height/15 + geom.size.height/20 + ) +#elseif os(visionOS) + .padding(.bottom, geom.size.height/25) +#elseif os(macOS) + .padding(.bottom, 15 + geom.size.height/20) +#else + .padding(.bottom, geom.size.height/20) +#endif + } + } +#if os(visionOS) + .padding(.top, geom.size.height/25) +#else + .padding(.top, geom.size.height/20) +#endif + } + + if configuration.dynamicTypeSize <= .xxxLarge { + Spacer() + +#if !os(tvOS) + configuration.footer +#if os(macOS) + .padding(30) +#else + .padding(.vertical, 5) +#endif +#endif + + configuration.dismissButton + .buttonStyle(ColorButtonStyle()) +#if os(iOS) + .padding( + .bottom, + 70 - geom.size.height/15 + geom.size.height/20 + ) +#elseif os(visionOS) + .padding(.bottom, geom.size.height/25) +#elseif os(macOS) + .padding(.bottom, 15 + geom.size.height/20) +#else + .padding(.bottom, geom.size.height/20) +#endif + } + } + .frame(maxWidth: .infinity,maxHeight: .infinity) + } +#if os(iOS) + .frame(maxWidth: 500) +#elseif os(macOS) + .frame(minWidth: 600,minHeight: 700, alignment: .center) +#elseif os(visionOS) + .frame(width: 655,height: 695, alignment: .center) +#endif + } +} + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +extension OnboardingViewStyle where Self == ClassicOnboardingViewStyle { + /// The regular About view style to use with an About view. + public static var classic: ClassicOnboardingViewStyle { + return ClassicOnboardingViewStyle() + } +} + + +#Preview { + OnboardingView(onboarding: PreviewWhatIsNewOnboarding()) + .onboardingViewStyle(.classic) +} diff --git a/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ColoredGlassOnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ColoredGlassOnboardingViewStyle.swift new file mode 100644 index 000000000..4f7b45d4b --- /dev/null +++ b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/ColoredGlassOnboardingViewStyle.swift @@ -0,0 +1,135 @@ +// +// GlassOnboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/04. +// + +import SwiftUI + +@available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) +@available(watchOS, unavailable) +public struct ColoredGlassOnboardingViewStyle: OnboardingViewStyle { + public func makeBody(configuration: Configuration) -> some View { + VStack(alignment: .center) { + ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 30) { + configuration.title + .font(.title2) + .fontWeight(.bold) + VStack(alignment: .leading, spacing: 20) { + configuration.content { feature in + if let image = feature.image { + Group { + if configuration.dynamicTypeSize <= .xxxLarge { + HStack(alignment: .top,spacing: 20) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle( + Color.accentColor + ) + .frame( + width: 37.5, + height: 37.5 + ) + .accessibilityHidden(true) + + VStack( + alignment: .leading, + spacing: 2.5 + ) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } else { + VStack(alignment: .leading) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle( + Color.accentColor + ) + .frame( + width: 37.5, + height: 37.5 + ) + .accessibilityHidden(true) + + VStack( + alignment: .leading, + spacing: 5 + ) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } + } +#if os(tvOS) + .focusable() +#endif + } + } + } + + if configuration.dynamicTypeSize > .xxxLarge { + configuration.footer + + configuration.dismissButton { text in + text + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.glassProminent) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding([.horizontal, .bottom],40) + .padding(.top, 80) + } + + if configuration.dynamicTypeSize <= .xxxLarge { + VStack(alignment: .center, spacing: 25) { + configuration.footer + + configuration.dismissButton { text in + text + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.glassProminent) + .padding(.horizontal, 40) + } + } + } + } +} + +@available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) +@available(watchOS, unavailable) +extension OnboardingViewStyle where Self == ColoredGlassOnboardingViewStyle { + /// The regular About view style to use with an About view. + public static var coloredGlass: ColoredGlassOnboardingViewStyle { + return ColoredGlassOnboardingViewStyle() + } +} + + +#Preview { + if #available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) { + OnboardingView(onboarding: PreviewWhatIsNewOnboarding()) + .onboardingViewStyle(.coloredGlass) + } +} diff --git a/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/GlassOnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/GlassOnboardingViewStyle.swift new file mode 100644 index 000000000..e31d66e56 --- /dev/null +++ b/Sources/OnboardingUI/UI/Style/OnboardingViewStyle/GlassOnboardingViewStyle.swift @@ -0,0 +1,135 @@ +// +// GlassOnboardingViewStyle.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/17. +// + +import SwiftUI + +@available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) +@available(watchOS, unavailable) +public struct GlassOnboardingViewStyle: OnboardingViewStyle { + public func makeBody(configuration: Configuration) -> some View { + VStack(alignment: .center) { + ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 30) { + configuration.title + .font(.title2) + .fontWeight(.bold) + VStack(alignment: .leading, spacing: 20) { + configuration.content { feature in + if let image = feature.image { + Group { + if configuration.dynamicTypeSize <= .xxxLarge { + HStack(alignment: .top,spacing: 20) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle( + Color.accentColor + ) + .frame( + width: 37.5, + height: 37.5 + ) + .accessibilityHidden(true) + + VStack( + alignment: .leading, + spacing: 2.5 + ) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } else { + VStack(alignment: .leading) { + image + .resizable() + .scaledToFit() + .font(.largeTitle) + .foregroundStyle( + Color.accentColor + ) + .frame( + width: 37.5, + height: 37.5 + ) + .accessibilityHidden(true) + + VStack( + alignment: .leading, + spacing: 5 + ) { + if let message = feature.message { + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) + } + } + } + } + } +#if os(tvOS) + .focusable() +#endif + } + } + } + + if configuration.dynamicTypeSize > .xxxLarge { + configuration.footer + + configuration.dismissButton { text in + text + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.glass) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding([.horizontal, .bottom],40) + .padding(.top, 80) + } + + if configuration.dynamicTypeSize <= .xxxLarge { + VStack(alignment: .center, spacing: 25) { + configuration.footer + + configuration.dismissButton { text in + text + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + } + .buttonStyle(.glass) + .padding(.horizontal, 40) + } + } + } + } +} + +@available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) +@available(watchOS, unavailable) +extension OnboardingViewStyle where Self == GlassOnboardingViewStyle { + /// The regular About view style to use with an About view. + public static var glass: GlassOnboardingViewStyle { + return GlassOnboardingViewStyle() + } +} + + +#Preview { + if #available(iOS 26, macOS 26, tvOS 26, visionOS 26, *) { + OnboardingView(onboarding: PreviewWhatIsNewOnboarding()) + .onboardingViewStyle(.glass) + } +} diff --git a/Sources/OnboardingUI/UI/View/OnboardingCardView.swift b/Sources/OnboardingUI/UI/View/OnboardingCardView.swift index 836e13d48..749eb7f97 100644 --- a/Sources/OnboardingUI/UI/View/OnboardingCardView.swift +++ b/Sources/OnboardingUI/UI/View/OnboardingCardView.swift @@ -38,8 +38,10 @@ public struct OnboardingCardView: View { image } content: { if let message = feature.message { - OnboardingSubtitle(feature.title) - OnboardingContent(message) + feature.title + .onboardingTextFormatting(style: .subtitle) + message + .onboardingTextFormatting(style: .content) } } } diff --git a/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift b/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift index 456f25338..f8b20398a 100644 --- a/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift +++ b/Sources/OnboardingUI/UI/View/OnboardingSheetView.swift @@ -38,7 +38,6 @@ public struct OnboardingSheetView: View { self.link = link() self.button = button() } - /// Creates an onboarding sheet view with a default link view. /// - Parameters: /// - title: Title View @@ -138,23 +137,30 @@ public struct OnboardingSheetView: View { } } -#Preview("Onboarding Sheet 2") { +#Preview("Onboarding Sheet") { OnboardingSheetView { - OnboardingTitle(String("Welcome to\nOnboardingUI")) + Text("Welcome to\nOnboardingUI") + .onboardingTextFormatting(style: .title) } content: { OnboardingItem(systemName: "keyboard",shape: .red) { - OnboardingSubtitle(String("Easy to Make")) - OnboardingContent(String("Onboarding screens like Apple's stock apps can be easily created with SwiftUI.")) + Text("Easy to Make") + .onboardingTextFormatting(style: .subtitle) + Text("Onboarding screens like Apple's stock apps can be easily created with SwiftUI.") + .onboardingTextFormatting(style: .content) } OnboardingItem(systemName: "macbook.and.ipad") { - OnboardingSubtitle(String("Not only for iPhone, but also for Mac and iPad")) - OnboardingContent(String("It supports not only iPhone, but also Mac and iPad. Therefore, there is no need to rewrite the code for each device.")) + Text("Not only for iPhone, but also for Mac and iPad") + .onboardingTextFormatting(style: .subtitle) + Text("It supports not only iPhone, but also Mac and iPad. Therefore, there is no need to rewrite the code for each device.") + .onboardingTextFormatting(style: .content) } OnboardingItem(systemName: "macbook.and.iphone",mode: .palette,primary: .primary,secondary: .blue) { - OnboardingSubtitle(String("Customize SF Symbols")) - OnboardingContent(String("It supports multi-colors and hierarchies supported by iOS 15 and macOS 12, so you can customize it as you wish.")) + Text("Customize SF Symbols") + .onboardingTextFormatting(style: .subtitle) + Text("It supports multi-colors and hierarchies supported by iOS 15 and macOS 12, so you can customize it as you wish.") + .onboardingTextFormatting(style: .content) } } link: { Link(String("Check our Privacy Policy…"), destination: URL(string: "https://kc-2001ms.github.io/en/privacy.html")!) diff --git a/Sources/OnboardingUI/UI/View/OnboardingView.swift b/Sources/OnboardingUI/UI/View/OnboardingView.swift new file mode 100644 index 000000000..ed1b9b79b --- /dev/null +++ b/Sources/OnboardingUI/UI/View/OnboardingView.swift @@ -0,0 +1,47 @@ +// +// OnboardingView.swift +// OnboardingUI +// +// Created by 茅根 啓介 on 2025/07/07. +// + +import SwiftUI + +@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) +@available(watchOS, unavailable) +public struct OnboardingView: View { + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + + @Environment( + \.onboardingViewStyle + ) private var onboardingViewStyle: AnyOnboardingViewStyle + + var onboarding: O + + init(onboarding: O) { + self.onboarding = onboarding + } + + public var body: some View { + onboardingViewStyle + .makeBody( + configuration: .init( + dynamicTypeSize: dynamicTypeSize, + title: .init(onboarding.title), + content: .init(onboarding.features), + footer: .init { + if let link = onboarding.link { + link + } + }, + dismissButton: .init(text: Text("Continue", bundle: .module)) + ) + ) + } +} + +#Preview { + if #available(iOS 18, macOS 15, tvOS 18, visionOS 2, *) { + OnboardingView(onboarding: PreviewWhatIsNewOnboarding()) + } +} diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift deleted file mode 100644 index 6af27a6fe..000000000 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingContent.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// OnboardingContent.swift -// -// -// Created by 茅根啓介 on 2023/03/09. -// - -import SwiftUI - -/// Content to be used for onboarding -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) -@available(watchOS, unavailable) -public struct OnboardingContent: View { - /// Text - var TextView: Text - /// Initialize with StringProtocol - /// - Parameter text: Display Contents - public init(_ content: S) where S : StringProtocol { - if content is String { - TextView = Text(LocalizedStringKey(content as! String)) - } else { - TextView = Text(content) - } - } - /// Creates a text view that displays localized content identified by a key. - /// - Parameters: - /// - key: The key for a string in the table identified by tableName. - /// - tableName: The name of the string table to search. If nil, use the table in the Localizable.strings file. - /// - bundle: The bundle containing the strings file. If nil, use the main bundle. - /// - comment: Contextual information about this key-value pair. - public init( - _ key: LocalizedStringKey, - tableName: String? = nil, - bundle: Bundle? = nil, - comment: StaticString? = nil - ){ - TextView = Text(key,tableName: tableName,bundle: bundle,comment: comment) - } - /// Initialize with String - /// - Parameter text: Display Contents - public init(verbatim content: String) { - TextView = Text(verbatim: content) - } - /// Initialize with AttributedString - /// - Parameter text: Display Contents - public init(_ attributedContent: AttributedString) { - TextView = Text(attributedContent) - } - /// Initialize with LocalizedStringResource - /// - Parameter text: Display Contents - public init(_ resource: LocalizedStringResource) { - TextView = Text(resource) - } - /// Initialize with Text - /// - Parameter text: Display Contents - public init(_ text: Text) { - self.TextView = text - } - /// View - public var body: some View { - TextView - .onboardingTextFormatting(style: .content) - } -} - -#Preview("OnboardingItemContent") { - OnboardingContent(String("Please check the display of this text")) -} diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift deleted file mode 100644 index 7e2247aea..000000000 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingSubtitle.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// OnboardingSubtitle.swift -// -// -// Created by 茅根啓介 on 2023/03/09. -// - -import SwiftUI - -/// Subtitle to be used for onboarding -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) -@available(watchOS, unavailable) -public struct OnboardingSubtitle: View { - /// Text - var TextView: Text - /// Initialize with StringProtocol - /// - Parameter text: Display Contents - public init(_ content: S) where S : StringProtocol { - if content is String { - TextView = Text(LocalizedStringKey(content as! String)) - } else { - TextView = Text(content) - } - } - /// Creates a text view that displays localized content identified by a key. - /// - Parameters: - /// - key: The key for a string in the table identified by tableName. - /// - tableName: The name of the string table to search. If nil, use the table in the Localizable.strings file. - /// - bundle: The bundle containing the strings file. If nil, use the main bundle. - /// - comment: Contextual information about this key-value pair. - public init( - _ key: LocalizedStringKey, - tableName: String? = nil, - bundle: Bundle? = nil, - comment: StaticString? = nil - ){ - TextView = Text(key,tableName: tableName,bundle: bundle,comment: comment) - } - /// Initialize with String - /// - Parameter text: Display Contents - public init(verbatim content: String) { - TextView = Text(verbatim: content) - } - /// Initialize with AttributedString - /// - Parameter text: Display Contents - public init(_ attributedContent: AttributedString) { - TextView = Text(attributedContent) - } - /// Initialize with LocalizedStringResource - /// - Parameter text: Display Contents - public init(_ resource: LocalizedStringResource) { - TextView = Text(resource) - } - /// Initialize with Text - /// - Parameter text: Display Contents - public init(_ text: Text) { - self.TextView = text - } - /// View - public var body: some View { - TextView - .onboardingTextFormatting(style: .subtitle) - } -} - -#Preview("OnboardingItemTitle") { - OnboardingSubtitle(String("Sample Subtitle")) -} diff --git a/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift b/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift deleted file mode 100644 index acc6dc5cf..000000000 --- a/Sources/OnboardingUI/UI/View/Parts/OnboardingTitle.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// OnboardingTitle.swift -// -// -// Created by 茅根啓介 on 2023/03/09. -// - -import SwiftUI - -/// Title to be used for onboarding -@available(iOS 17.0,macOS 14.0,tvOS 17.0,visionOS 1.0,*) -@available(watchOS, unavailable) -public struct OnboardingTitle: View { - /// Text - var TextView: Text - /// Initialize with StringProtocol - /// - Parameter text: Display Contents - public init(_ content: S) where S : StringProtocol { - if content is String { - TextView = Text(LocalizedStringKey(content as! String)) - } else { - TextView = Text(content) - } - } - /// Creates a text view that displays localized content identified by a key. - /// - Parameters: - /// - key: The key for a string in the table identified by tableName. - /// - tableName: The name of the string table to search. If nil, use the table in the Localizable.strings file. - /// - bundle: The bundle containing the strings file. If nil, use the main bundle. - /// - comment: Contextual information about this key-value pair. - public init( - _ key: LocalizedStringKey, - tableName: String? = nil, - bundle: Bundle? = nil, - comment: StaticString? = nil - ){ - TextView = Text(key,tableName: tableName,bundle: bundle,comment: comment) - } - /// Initialize with String - /// - Parameter text: Display Contents - public init(verbatim content: String) { - TextView = Text(verbatim: content) - } - /// Initialize with AttributedString - /// - Parameter text: Display Contents - public init(_ attributedContent: AttributedString) { - TextView = Text(attributedContent) - } - /// Initialize with LocalizedStringResource - /// - Parameter text: Display Contents - public init(_ resource: LocalizedStringResource) { - TextView = Text(resource) - } - /// Initialize with Text - /// - Parameter text: Display Contents - public init(_ text: Text) { - self.TextView = text - } - /// View - public var body: some View { - TextView - .onboardingTextFormatting(style: .title) - } -} - - -#Preview("OnboardingTitle") { - OnboardingTitle(String("Sample Title")) -} From 8f66e4421754d4aa2610e1e0cb7999ac58cf5d70 Mon Sep 17 00:00:00 2001 From: KC-2001MS Date: Thu, 17 Jul 2025 18:14:37 +0900 Subject: [PATCH 5/5] Name Correction --- ...ngTextFormatting.swift => Text+onboardingTextFormatting.swift} | 0 .../{OnboardingSheet.swift => View+onboardingSheet.swift} | 0 .../{OnboardingViewStyle.swift => View+onboardingViewStyle.swift} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename Sources/OnboardingUI/UI/Modifier/{OnboardingTextFormatting.swift => Text+onboardingTextFormatting.swift} (100%) rename Sources/OnboardingUI/UI/Modifier/{OnboardingSheet.swift => View+onboardingSheet.swift} (100%) rename Sources/OnboardingUI/UI/Modifier/{OnboardingViewStyle.swift => View+onboardingViewStyle.swift} (100%) diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingTextFormatting.swift b/Sources/OnboardingUI/UI/Modifier/Text+onboardingTextFormatting.swift similarity index 100% rename from Sources/OnboardingUI/UI/Modifier/OnboardingTextFormatting.swift rename to Sources/OnboardingUI/UI/Modifier/Text+onboardingTextFormatting.swift diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift b/Sources/OnboardingUI/UI/Modifier/View+onboardingSheet.swift similarity index 100% rename from Sources/OnboardingUI/UI/Modifier/OnboardingSheet.swift rename to Sources/OnboardingUI/UI/Modifier/View+onboardingSheet.swift diff --git a/Sources/OnboardingUI/UI/Modifier/OnboardingViewStyle.swift b/Sources/OnboardingUI/UI/Modifier/View+onboardingViewStyle.swift similarity index 100% rename from Sources/OnboardingUI/UI/Modifier/OnboardingViewStyle.swift rename to Sources/OnboardingUI/UI/Modifier/View+onboardingViewStyle.swift