Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save TizianoCoroneo/ce0e9cab877b9dab59c0650308fca4de to your computer and use it in GitHub Desktop.
Save TizianoCoroneo/ce0e9cab877b9dab59c0650308fca4de to your computer and use it in GitHub Desktop.
Cool home navbar scrolling animation in SwiftUI
import SwiftUI
struct ContentView: View {
@State var currentOffset: CGFloat = 0
@State var topMessagePadding: CGFloat = 30
@State var fadeAlpha: Double = 0
@State var navBarFadeAlpha: Double = 0
@State var imageVerticalOffset: CGFloat = 0
@State var titleVerticalOffset: CGFloat = 0
let coordinateSpaceName: String = "Background space"
var body: some View {
ZStack(alignment: .top) {
BackgroundView()
ScrollView {
ZStack(alignment: .top) {
GeometryReader { proxy in
Color.clear
.onChange(
of: proxy.frame(in: .named(coordinateSpaceName))
) { value in
currentOffset = value.minY
- topMessagePadding
}
.onAppear {
currentOffset = proxy
.frame(in: .named(coordinateSpaceName))
.minY - topMessagePadding
}
}
.frame(height: 0)
IllustratedCardView(imageVerticalOffset: imageVerticalOffset)
.overlay(Color.white.opacity(fadeAlpha))
HStack {
FeedTitle()
Spacer()
}
.padding(.horizontal)
.padding(.top, 120 + titleVerticalOffset - topMessagePadding)
HomeFeedView()
.padding(.top, 480)
.padding(.top, -40)
}
.padding(.vertical, topMessagePadding)
}
.coordinateSpace(name: coordinateSpaceName)
SearchBar()
.padding()
.padding(.top, topMessagePadding)
.background(Color.white.opacity(navBarFadeAlpha).ignoresSafeArea())
}
.overlay(
Text("\(currentOffset)")
.font(.largeTitle)
.foregroundColor(.red),
alignment: .bottomTrailing)
.onChange(of: currentOffset, perform: { value in
self.topMessagePadding = min(30, max(-16, 0.4 * value + 30 ))
self.fadeAlpha = min(1, max(0, -1 * Double(30 + value) / 400 ))
self.navBarFadeAlpha = min(1, max(0, -1 * Double(30 + value) / 100 ))
self.imageVerticalOffset = value / 4
self.titleVerticalOffset = -value
})
}
}
struct ScrollMeasureGuideID: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[.top]
}
}
extension VerticalAlignment {
static let scrollMeasure = VerticalAlignment(ScrollMeasureGuideID.self)
}
struct SearchBar: View {
var body: some View {
Button {
// TODO: Go to search
} label: {
HStack {
Spacer()
Image(systemName: "magnifyingglass")
.foregroundColor(.red)
Text("Where are you going?")
.foregroundColor(.black)
.font(.caption)
Spacer()
}
.padding(.vertical, 8)
.background(
Capsule(style: .circular)
.foregroundColor(.white))
}
}
}
struct FeedTitle: View {
var body: some View {
VStack(alignment: .leading) {
Text("Go\nNear")
.fontWeight(.bold)
.font(.largeTitle)
.lineLimit(nil)
.lineSpacing(0)
.foregroundColor(.white)
.layoutPriority(10)
Button {
// TODO: Go somewhere
} label: {
Text("Explore nearby stays")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.black)
.padding(8)
.background(
RoundedRectangle(cornerRadius: 6)
.foregroundColor(.white))
}
}
}
}
struct IllustratedCardView: View {
let imageVerticalOffset: CGFloat
var body: some View {
Image("windmill")
.resizable()
.scaledToFit()
.offset(x: 0, y: imageVerticalOffset)
.clipShape(RoundedRectangle(cornerRadius: 25))
}
}
struct HomeFeedView: View {
var body: some View {
VStack(alignment: .leading) {
ScrollView(
.horizontal,
showsIndicators: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/
) {
LazyHGrid(
rows: [
GridItem(.fixed(60)),
GridItem(.fixed(60)),
],
alignment: .top,
spacing: 10
) {
ForEach(0...10, id: \.self) { _ in
HomeFeedCard()
}
}
.padding(.horizontal)
.padding(.bottom, 12)
}
.frame(height: 140)
.padding(.vertical)
Text("Live anywhere")
.font(.title3)
.fontWeight(.bold)
.padding(.horizontal)
ScrollView(
.horizontal,
showsIndicators: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/
) {
HStack(spacing: 12) {
ForEach(1...10, id: \.self) { _ in
HomeBigCard()
}
}
.padding(.horizontal)
.padding(.bottom, 12)
}
.padding(.bottom)
Color.black
.frame(height: 500)
Spacer()
.layoutPriority(10)
}
.background(Color.white)
.zIndex(100)
}
}
struct HomeFeedCard: View {
var body: some View {
HStack {
Image("windmill")
.resizable()
.scaledToFill()
.frame(width: 60, height: 60)
.clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading) {
Text("Penzance")
.font(.caption)
.fontWeight(.semibold)
Text("5.5-hour drive")
.font(.caption2)
}
}
}
}
struct HomeBigCard: View {
var body: some View {
VStack(alignment: .leading) {
Image("windmill")
.resizable()
.scaledToFill()
.frame(width: 300, height: 300)
.clipShape(RoundedRectangle(cornerRadius: 10))
Text("Entire homes")
.font(.caption)
.fontWeight(.semibold)
}
}
}
struct BackgroundView: View {
var body: some View {
VStack {
Button {
// TODO: Go somewhere
} label: {
Text("Get the latest on our COVID-19 response")
.font(.callout)
.underline()
.foregroundColor(.white)
}
Spacer()
}
.frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/)
.background(Color.black.ignoresSafeArea())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment