Skip to content

Instantly share code, notes, and snippets.

@leojquinteros
Last active June 19, 2023 16:37
Show Gist options
  • Save leojquinteros/4cf56f12f8e7c59711269c7a3f5e1328 to your computer and use it in GitHub Desktop.
Save leojquinteros/4cf56f12f8e7c59711269c7a3f5e1328 to your computer and use it in GitHub Desktop.
Just playing around with a collapsable header using SwiftUI
//
// CollapsableHeader.swift
//
// Created by Leo Quinteros on 25/06/22.
//
import SwiftUI
struct Message: Identifiable {
var id = UUID().uuidString
var message, username: String
var tintColor: Color
}
let messageText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
let usernameText = "Lorem ipsum"
var allMessages: [Message] {
[Message](repeating: .init(message: messageText, username: usernameText, tintColor: .primary), count: 30)
}
struct OffsetModifier: ViewModifier {
@Binding var offset: CGFloat
func body(content: Content) -> some View {
content
.overlay(
GeometryReader { proxy -> Color in
let minY = proxy.frame(in: .named("scroll")).minY
DispatchQueue.main.async {
self.offset = minY
}
return Color.clear
},
alignment: .top
)
}
}
struct CustomCorner: Shape {
var corners: UIRectCorner
var radius: CGFloat
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
struct HomeView: View {
let maxHeight = UIScreen.main.bounds.height / 2.3
var topEdge: CGFloat
@State var offset: CGFloat = 0
var headerHeight: CGFloat {
let topHeight = maxHeight + offset
return topHeight > 80 + topEdge ? topHeight : 80 + topEdge
}
var cornerRadius: CGFloat {
let progress = -offset / (maxHeight - (80 + topEdge))
let radius = (1 - progress) * 50
return offset < 0 ? radius : 50
}
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 15) {
GeometryReader { proxy in
TopBar(topEdge: topEdge, offset: $offset, maxHeight: maxHeight)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.frame(height: headerHeight, alignment: .bottom)
.background(
Color.blue, in: CustomCorner(corners: [.bottomRight], radius: cornerRadius)
)
.overlay(NavBar(topEdge: topEdge, offset: $offset, maxHeight: maxHeight), alignment: .top)
}
.frame(height: maxHeight)
.offset(y: -offset)
.zIndex(1)
VStack(spacing: 15) {
ForEach(allMessages) { message in
MessageCardView(message: message)
}
}
.padding()
.zIndex(0)
}
.modifier(OffsetModifier(offset: $offset))
}
.coordinateSpace(name: "scroll")
}
}
struct NavBar: View {
let topEdge: CGFloat
@Binding var offset: CGFloat
var maxHeight: CGFloat
var topBarOpacity: CGFloat {
-(offset + 70) / (maxHeight - (80 + topEdge))
}
var body: some View {
HStack(alignment: .center, spacing: 15) {
Button(action: {
}, label: {
Image(systemName: "xmark")
.font(.body.bold())
})
Image("pic")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 35, height: 35)
.clipShape(Circle())
.opacity(topBarOpacity)
Text("Lorem ipsum")
.fontWeight(.bold)
.foregroundColor(.white)
.opacity(topBarOpacity)
Spacer()
Button {
} label: {
Image(systemName: "line.3.horizontal.decrease")
.font(.body.bold())
}
}
.padding(.horizontal)
.frame(height: 80)
.foregroundColor(.white)
.padding(.top, topEdge)
}
}
struct TopBar: View {
let topEdge: CGFloat
@Binding var offset: CGFloat
var maxHeight: CGFloat
var barOpacity: CGFloat {
let progress = -offset / 70
let opactity = 1 - progress
return offset < 0 ? opactity : 1
}
var body: some View {
VStack(alignment: .leading, spacing: 15) {
Image("pic")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 80, height: 80, alignment: .center)
.cornerRadius(10)
Text("Lorem ipsum")
.font(.largeTitle.bold())
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
.fontWeight(.semibold)
.foregroundColor(.white)
.opacity(0.8)
}
.padding()
.padding(.bottom)
.opacity(barOpacity)
}
}
struct MessageCardView: View {
var message: Message
var body: some View {
HStack(spacing: 15) {
Circle()
.fill(message.tintColor)
.frame(width: 50, height: 50, alignment: .center)
.opacity(0.8)
VStack(alignment: .leading, spacing: 8) {
Text(message.username)
.fontWeight(.bold)
Text(message.message)
.foregroundColor(.secondary)
}
.foregroundColor(.primary)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
HomeView(topEdge: proxy.safeAreaInsets.top)
.ignoresSafeArea(.all, edges: .top)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@main
struct collapsableheaderApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment