title: Collapsing Top Bar in SwiftUI
body: <p>I have spent that past few days looking for a collapsing top bar in SwiftUI. I currently have a solution that I made from <a href="https://martinmitrevski.com/2020/04/17/collapsing-top-bar-animation-with-swiftui/" rel="nofollow noreferrer">this article</a>. I have adjusted the original code slightly, but I still encounter a bug when I scroll to the bottom of the ScrollView:</p>
  <p><a href="https://i.stack.imgur.com/qivBE.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/qivBE.gif" alt="enter image description here" /></a></p>
  <p>My goal is to have a top bar like the one from Twitter below:</p>
  <p><a href="https://i.stack.imgur.com/CTUbF.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/CTUbF.gif" alt="enter image description here" /></a></p>
  <p>While I may be able to find a solution to the bug, I was hoping to find a more stable and customizable top bar in SwiftUI. I have looked for libraries and other related solutions to making a custom top bar, but I have not found anything that good.</p>
  <p>Here is my code:</p>
  <p><strong>ContentView.swift</strong></p>
  <pre><code>struct ContentView: View {
      @State var initialOffset: CGFloat?
      @State var offset: CGFloat?
      @State var previousOffset: CGFloat?
      
      var isOffset : Bool {
          // previousOffset! &lt; offset
          if TopBar.heightAndRadiusForBackground(initialOffset: self.initialOffset, offset: self.offset, previousOffset: self.previousOffset) != 160.0 {
              return true
          }
          else {
              return false
          }
      }
      
      var body: some View {
          VStack {
              TopBar(offset: self.$offset,
                     initialOffset: self.$initialOffset,
                     previousOffset: self.$previousOffset)
              
              ScrollView {
                  GeometryReader { geometry in
                      Color.clear
                          .preference(key: OffsetKey.self,
                                      value: geometry.frame(in: .global).minY
                      )
                          .frame(height: 0)
                  }
                  LazyVStack {
                      ForEach((1...10), id: \.self) {
                          Text(&quot;\($0)&quot;)
                              .font(.title)
                              .bold()
                              .frame(width: UIScreen.main.bounds.width, height: 200, alignment: .center)
                      }
                  }
              }
              .padding(.top, self.isOffset ? -16 : 40)
              .animation(.easeOut)
          }
  
          .onPreferenceChange(OffsetKey.self) {
              if self.initialOffset == nil || self.initialOffset == 0 {
                  self.initialOffset = $0
              }
              if self.offset != nil {
                  self.previousOffset = self.offset
              }
              self.offset = $0
          }
      }
  }
  </code></pre>
  <p><strong>TopBar.swift</strong></p>
  <pre><code>struct TopBar: View {
      @Binding var offset: CGFloat?
      @Binding var initialOffset: CGFloat?
      @Binding var previousOffset: CGFloat?
      
      var isOffset : Bool {
          if TopBar.heightAndRadiusForBackground(initialOffset: self.initialOffset, offset: self.offset, previousOffset: self.previousOffset) != 160.0 {
              return true
          }
          else {
              return false
          }
      }
      
      func viewForBackground() -&gt; some View {
          let newHeight = TopBar.heightAndRadiusForBackground(initialOffset: self.initialOffset, offset: self.offset, previousOffset: self.previousOffset)
          return Rectangle()
              .foregroundColor(.green)
              .frame(width: UIScreen.main.bounds.width, height: newHeight)
              .offset(y: self.isOffset ? -32 : -12)
      }
      
      var body: some View {
          HStack {
              Text(&quot;Top Bar&quot;)
                  .font(.system(size: 34, weight: .bold, design: .default))
              Spacer()
          }
          .offset(y: self.isOffset ? -16 : UIScreen.main.bounds.height/28)
          .padding(.leading)
          .animation(.easeInOut)
          .background(viewForBackground().animation(.easeOut))
      }
  }
  
  extension TopBar {
      static func heightAndRadiusForBackground(initialOffset: CGFloat?, offset: CGFloat?, previousOffset: CGFloat?) -&gt; CGFloat {
          let maxHeight: CGFloat = 160.0
          let minHeight: CGFloat = 90
          
          guard let initialOffset = initialOffset,
                let offset = offset else { return maxHeight }
          
          if previousOffset == nil {
              return maxHeight
          }
          
          // Previous &lt; offset: Scrolling down
          // Previous &gt; offset: Scrolliing up
          if initialOffset &gt; offset {
              if previousOffset! &lt; offset {
                  return maxHeight
              }
              else {
                  return minHeight
              }
          }
          
          return maxHeight
      }
  }
  
  
  struct OffsetKey: PreferenceKey {
      static let defaultValue: CGFloat? = nil
      static func reduce(value: inout CGFloat?,
                         nextValue: () -&gt; CGFloat?) {
          value = value ?? nextValue()
      }
  }
  </code></pre>
  <p><a href="https://drive.google.com/file/d/1nCbNV861HF7-_PKgZV7AQGc_3vAHVCel/view?usp=sharing" rel="nofollow noreferrer">Project</a></p>