Skip to content

Instantly share code, notes, and snippets.

@Chronos2500
Created March 7, 2025 16:19
Show Gist options
  • Select an option

  • Save Chronos2500/66d1b18fda49a5c0c83485ab1f9fc321 to your computer and use it in GitHub Desktop.

Select an option

Save Chronos2500/66d1b18fda49a5c0c83485ab1f9fc321 to your computer and use it in GitHub Desktop.

Revisions

  1. Chronos2500 created this gist Mar 7, 2025.
    77 changes: 77 additions & 0 deletions ItemAnchorSampleView.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    import SwiftUI

    //自作パッケージの切り出しなので変数名がめちゃくちゃです
    //詳しくはこれの実装を見てください https://github.com/Chronos2500/CustomNavigationTitle

    struct ContentView: View {
    var body: some View {
    NavigationStack{
    ScrollView {
    Color.blue.frame(height: 200)
    .titleVisibilityAnchor()
    Text("サンプルページ")
    .frame(maxWidth: .infinity,alignment: .center)
    }
    .scrollAwareTitle()
    }
    }
    }

    struct BoundsPreferenceKey: PreferenceKey {
    typealias Value = Anchor<CGRect>?
    static let defaultValue: Value = nil
    static func reduce(value: inout Value, nextValue: () -> Value) {
    guard let newValue = nextValue() else { return }
    value = newValue
    }
    }

    extension View {
    public func titleVisibilityAnchor() -> some View {
    self.anchorPreference(
    key: BoundsPreferenceKey.self,
    value: .bounds
    ) { anchor in
    anchor
    }
    }
    }

    private struct ScrollAwareTitleModifier: ViewModifier {
    @State var isShowNavigationTitle = false
    func body(content: Content) -> some View {
    content
    .backgroundPreferenceValue(BoundsPreferenceKey.self) { anchor in
    GeometryReader { proxy in
    if let anchor = anchor {
    let scrollFrame = proxy.frame(in: .local).minY
    let itemFrame = proxy[anchor]
    let isVisible = (itemFrame.maxY + 22) > scrollFrame
    DispatchQueue.main.async{
    if isVisible {
    isShowNavigationTitle = false
    } else if !isVisible {
    isShowNavigationTitle = true
    }
    }
    }
    return Color.clear
    }
    }
    .toolbarBackgroundVisibility(.hidden, for: .navigationBar)
    .toolbar {
    ToolbarItem(placement: .topBarTrailing) {
    Image(systemName: "xmark.circle.fill")
    .imageScale(.large)
    .foregroundStyle(isShowNavigationTitle ? .blue : .red)
    .animation(.easeIn(duration: 0.15), value: isShowNavigationTitle)
    }
    }
    }
    }

    extension View {
    public func scrollAwareTitle() -> some View {
    modifier(ScrollAwareTitleModifier())
    }
    }