Skip to content

Instantly share code, notes, and snippets.

@simme
Last active February 2, 2020 18:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simme/33d332912784d4a8208fc91c8aeead43 to your computer and use it in GitHub Desktop.
Save simme/33d332912784d4a8208fc91c8aeead43 to your computer and use it in GitHub Desktop.
import SwiftUI
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
struct ExperimentTres: View {
var body: some View {
GeometryReader { g in
ScrollView {
VStack(spacing: 0) {
ScrollingHeader(safeAreaTop: g.safeAreaInsets.top)
.aspectRatio(0.9, contentMode: .fill)
.zIndex(10)
Color.blue.frame(height: 3000)
}
}
}.edgesIgnoringSafeArea(.vertical)
}
}
struct MightyFineButton: View {
var isHidden: Bool
var body: some View {
Circle()
.frame(width: 28, height: 28, alignment: .center)
}
}
struct ScrollingHeader: View {
let safeAreaTop: CGFloat
let standardNavBarHeight: CGFloat = 44
@State private var buttonsInNavBar: Bool = true
var body: some View {
GeometryReader { g in
ZStack {
Color.red
HStack {
if !self.buttonsInNavBar {
MightyFineButton(isHidden: self.buttonsInNavBar)
.transition(.scale)
}
Spacer()
if !self.buttonsInNavBar {
MightyFineButton(isHidden: self.buttonsInNavBar)
.transition(.scale)
}
}
.padding(.horizontal, 16)
.frame(height: 32)
.padding(.top, self.safeAreaTop)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
.offset(y: {
let o = -g.frame(in: .global).minY
guard o > 0 else { return 0 }
let remaining = o - g.size.height
if remaining > -self.standardNavBarHeight * 2 - self.safeAreaTop {
DispatchQueue.main.async { withAnimation { self.buttonsInNavBar = true } }
return min(g.size.height - self.standardNavBarHeight * 2 - self.safeAreaTop, o)
}
DispatchQueue.main.async { withAnimation { self.buttonsInNavBar = false } }
return o
}())
HStack {
if self.buttonsInNavBar {
MightyFineButton(isHidden: self.buttonsInNavBar)
.transition(.scale)
.animation(.spring(response: 1, dampingFraction: 0.4, blendDuration: 1))
}
Spacer()
Text("Title").font(.headline)
Spacer()
if self.buttonsInNavBar {
MightyFineButton(isHidden: self.buttonsInNavBar)
.animation(.spring(response: 1, dampingFraction: 0.4, blendDuration: 1))
.transition(.scale)
}
}
.padding(.horizontal, 16)
.padding(.bottom, 8)
.frame(height: {
// When approaching the top we have to add that extra height to the navbar.
let o = -g.frame(in: .global).minY
let remaining = o - g.size.height
let navBarHeight: CGFloat = self.standardNavBarHeight
let safeArea: CGFloat = self.safeAreaTop
if remaining > -self.standardNavBarHeight - self.safeAreaTop {
return min(
CGFloat(navBarHeight + safeArea),
navBarHeight + max(0, safeArea - (-o + g.size.height) + navBarHeight))
}
return self.standardNavBarHeight
}(), alignment: .bottom)
.background(Color.white.opacity(0.5))
.cornerRadius({
let remaining = g.frame(in: .global).minY + g.size.height - self.standardNavBarHeight
let progress = max(0, min(1 - (remaining / self.safeAreaTop), 1))
// None-notch iPhones
if self.safeAreaTop == 20 {
return 20 - (20 * progress)
} else {
return 20 + (20 * progress)
}
}(), corners: [.topLeft, .topRight])
.shadow(radius: 4)
// Allows the shadow to show on the top, but not under.
.mask(Rectangle().padding(.top, -10))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .bottom)
}
.offset(y: {
let o = -g.frame(in: .global).minY
guard o > 0 else { return o }
// When scrolling down stop the view when only the navbar is visible.
let remaining = o - g.size.height
if remaining > -self.standardNavBarHeight - self.safeAreaTop {
return o - (g.size.height - self.standardNavBarHeight - self.safeAreaTop)
}
return 0
}())
.frame(height: {
// Makes the header stretchy when pulling the scroll view down by adding the offset to the height.
let standardHeight = g.size.height
let o = g.frame(in: .global).minY
let extraHeight = o > 0 ? o : 0
return standardHeight + extraHeight
}())
}
}
}
struct ExperimentTres_Previews: PreviewProvider {
static var previews: some View {
Group {
ExperimentTres()
ExperimentTres().previewDevice("iPhone 8")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment