diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3f57ef1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: Build + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: build-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build (${{ matrix.platform }}) + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + include: + - platform: iOS + destination: generic/platform=iOS Simulator + - platform: Mac Catalyst + destination: generic/platform=macOS,variant=Mac Catalyst + - platform: macOS + destination: platform=macOS + + steps: + - uses: actions/checkout@v6 + - name: Build + run: >- + xcodebuild + -scheme CustomNavigationTitle + -destination '${{ matrix.destination }}' + CODE_SIGNING_ALLOWED=NO + build diff --git a/Assets/demo02.gif b/Assets/demo02.gif new file mode 100644 index 0000000..0f72a0b Binary files /dev/null and b/Assets/demo02.gif differ diff --git a/Examples/CustomNavigationTitleExample/CustomNavigationTitleExample/ContentView.swift b/Examples/CustomNavigationTitleExample/CustomNavigationTitleExample/ContentView.swift index 5a21fa7..d915951 100644 --- a/Examples/CustomNavigationTitleExample/CustomNavigationTitleExample/ContentView.swift +++ b/Examples/CustomNavigationTitleExample/CustomNavigationTitleExample/ContentView.swift @@ -13,7 +13,7 @@ struct ContentView: View { NavigationStack{ List { Section{ - VStack(spacing: 10){ + VStack(alignment: .leading, spacing: 10){ Image(systemName: "hand.raised.app.fill") .resizable() .scaledToFit() @@ -24,12 +24,11 @@ struct ContentView: View { .bold() .titleVisibilityAnchor() Text("Control which apps can access your data, location, camera, and microphone, and manage safety protections. [Learn more..](https://www.apple.com).") - .multilineTextAlignment(.center) + .multilineTextAlignment(.leading) } - .padding(22) - .frame(maxWidth: .infinity, alignment: .center) - .overlay(alignment: .topLeading){ + .frame(maxWidth: .infinity, alignment: .leading) + .overlay(alignment: .top){ Text("This is Demo") .fontWeight(.heavy) .foregroundStyle(.red) @@ -43,7 +42,7 @@ struct ContentView: View { } } #if canImport(UIKit) - .listStyle(.insetGrouped) + .listStyle(.automatic) #endif .scrollAwareTitle("Privacy & Security") .navigationTitle("Hello!") diff --git a/README-ja.md b/README-ja.md index f60b649..111f925 100644 --- a/README-ja.md +++ b/README-ja.md @@ -6,7 +6,9 @@ CustomNavigationTitleは、スクロールに応じてナビゲーションタイトルをアニメーション付きで表示・非表示にするSwiftUI用のシンプルなパッケージです。(この効果はApple純正の設定アプリ、メールアプリ、AppStoreアプリで使われています。) -![Demo](Assets/demo01.gif) +| | | +|---|---| +| | | ## 特徴 - スクロールに応じてタイトルがフェードイン・アウト diff --git a/README.md b/README.md index 00505c5..a4dda60 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ CustomNavigationTitle is a simple SwiftUI package that animates the display and hiding of the navigation title based on scrolling. (This effect is used in Apple's native Settings, Mail, and App Store apps.) -![Demo](Assets/demo01.gif) +| | | +|---|---| +| | | ## Features - The title fades in and out based on scrolling. diff --git a/Sources/CustomNavigationTitle/CustomNavigationTitle.swift b/Sources/CustomNavigationTitle/CustomNavigationTitle.swift index d1ae290..02d5ded 100644 --- a/Sources/CustomNavigationTitle/CustomNavigationTitle.swift +++ b/Sources/CustomNavigationTitle/CustomNavigationTitle.swift @@ -20,10 +20,20 @@ extension View { } } -private struct ScrollAwareTitleModifier: ViewModifier { +struct ScrollAwareTitleModifier: ViewModifier { @State private var isShowNavigationTitle = false @Environment(\.scrollAwareTitleAnimation) private var animation + let title: V + let preferredSolariumEffect: Bool + var useSolariumEffect: Bool { + #if os(iOS) + if #available(iOS 26.0, *) { + return preferredSolariumEffect + } + #endif + return false + } func body(content: Content) -> some View { content @@ -43,12 +53,14 @@ private struct ScrollAwareTitleModifier: ViewModifier { .toolbar { ToolbarItem(placement: .principal) { title - .bold() + .font(titleFont) .lineLimit(1) .opacity(isShowNavigationTitle ? 1 : 0) - .animation(animation, value: isShowNavigationTitle) + .blur(radius: preferredSolariumEffect ? (isShowNavigationTitle ? 0 : 4) : 0) + .offset(y: preferredSolariumEffect ? (isShowNavigationTitle ? 0 : 15) : 0) + .animation(resolvedAnimation, value: isShowNavigationTitle) } -#if compiler(>=6.2) + #if compiler(>=6.2) .modifier{ if #available(iOS 26.0, macOS 26.0, macCatalyst 26.0, *) { $0.sharedBackgroundVisibility(.hidden) @@ -56,33 +68,61 @@ private struct ScrollAwareTitleModifier: ViewModifier { $0 } } -#endif + #endif } -#if canImport(UIKit) + #if canImport(UIKit) .navigationBarTitleDisplayMode(.inline) -#endif + #endif + } + + private var titleFont: Font { + if #available(iOS 26.0, macOS 26.0, macCatalyst 26.0, *) { + // UICTFontTextStyleHeadline, weight: .semibold + Font.headline.weight(.semibold) + } else { + // UICTFontTextStyleShortEmphasizedBody + Font.body.bold().leading(.tight) + } + } + + private var resolvedAnimation: Animation { + if preferredSolariumEffect, animation == nil { + let u = 0.5 // min(abs(scrollVelocity.dy) / 2, 1) + let durationScale = 1 - 0.5 * u + + return isShowNavigationTitle + ? .spring(duration: 0.45 * durationScale, bounce: 0.4 * u) + : .spring(duration: 0.70 * durationScale, bounce: 0.4 * u) + } + return animation ?? .easeIn(duration: 0.15) } } + + + extension View { public func scrollAwareTitle(@ViewBuilder _ title: () -> V) -> some View { - modifier(ScrollAwareTitleModifier(title: title())) + modifier(ScrollAwareTitleModifier(title: title(), preferredSolariumEffect: false)) + } + internal func _scrollAwareTitle(@ViewBuilder _ title: () -> V) -> some View { + modifier(ScrollAwareTitleModifier(title: title(), preferredSolariumEffect: true)) } } extension View { @_disfavoredOverload public func scrollAwareTitle(_ title: S) -> some View { - scrollAwareTitle{ + _scrollAwareTitle{ Text(title) } } public func scrollAwareTitle(_ title: LocalizedStringKey) -> some View { - scrollAwareTitle{ + _scrollAwareTitle{ Text(title) } } @_disfavoredOverload public func scrollAwareTitle(_ title: LocalizedStringResource) -> some View { - scrollAwareTitle{ + _scrollAwareTitle{ Text(title) } } diff --git a/Sources/CustomNavigationTitle/ScrollAwareTitleAnimation.swift b/Sources/CustomNavigationTitle/ScrollAwareTitleAnimation.swift index 3b6c967..481b474 100644 --- a/Sources/CustomNavigationTitle/ScrollAwareTitleAnimation.swift +++ b/Sources/CustomNavigationTitle/ScrollAwareTitleAnimation.swift @@ -1,11 +1,11 @@ import SwiftUI extension EnvironmentValues { - @Entry var scrollAwareTitleAnimation: Animation = .easeIn(duration: 0.15) + @Entry var scrollAwareTitleAnimation: Animation? = nil } extension View { - public func scrollAwareTitleAnimation(_ animation: Animation) -> some View { + public func scrollAwareTitleAnimation(_ animation: Animation?) -> some View { environment(\.scrollAwareTitleAnimation, animation) } }