Created
April 24, 2021 15:08
-
-
Save TizianoCoroneo/ce0e9cab877b9dab59c0650308fca4de to your computer and use it in GitHub Desktop.
Cool home navbar scrolling animation in SwiftUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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