技术:SwiftUI、SwiftUI3.0、可折叠、动画标题、滚动视图
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max
SwiftUI创建时尚的可折叠动画标题
- 概述
- 详细
- 一、运行效果
- 二、项目结构图
- 三、程序实现 - 过程
- 1.创建一个项目命名为 `CollapsibleHeader`
- 1.1.引入资源文件和颜色
- 2. 创建一个虚拟文件`New Group` 命名为 `View`
- 3. 创建一个虚拟文件`New Group` 命名为 `Model`
- 4. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Home`
- 5. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`CustomCorner`
- 6. 创建一个虚拟文件`New Group` 命名为 `Modifiers`
- 7. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`OffsetModifier`
- 8. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Message` 并且继承于`Identifiable` 作为模型
- Code
概述
使用SwiftUI创建时尚的可折叠动画标题
详细
一、运行效果
二、项目结构图
三、程序实现 - 过程
思路:
- 搭建导航整体拉伸模块
- 消息模块
- 进行滚动 大于或者小于导航高度 处理 展示拉伸或者隐藏导航头像标题模块
1.创建一个项目命名为 CollapsibleHeader
1.1.引入资源文件和颜色
颜色
TopBar#451AF5
头像图片1张
2. 创建一个虚拟文件New Group
命名为 View
3. 创建一个虚拟文件New Group
命名为 Model
4. 创建一个文件New File
选择SwiftUI View
类型 命名为Home
5. 创建一个文件New File
选择SwiftUI View
类型 命名为CustomCorner
主要是: 用于设置自定义圆角区域
6. 创建一个虚拟文件New Group
命名为 Modifiers
7. 创建一个文件New File
选择SwiftUI View
类型 命名为OffsetModifier
8. 创建一个文件New File
选择SwiftUI View
类型 命名为Message
并且继承于Identifiable
作为模型
Code
ContentView - 主窗口
主要是展示主窗口
Home
和获取安全区域
//
// ContentView.swift
// Shared
//
// Created by 李宇鸿 on 2022/9/9.
//
import SwiftUI
struct ContentView: View {
var body: some View {
// 忽略并得到安全区域的大小…
GeometryReader{proxy in
let topEdge = proxy.safeAreaInsets.top
Home(topEdge:topEdge)
.ignoresSafeArea(.all,edges: .top)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Home - 主页
思路
- 搭建导航整体拉伸模块
- 消息模块
- 进行滚动 大于或者小于导航高度 处理 展示拉伸或者隐藏导航头像标题模块
//
// Home.swift
// CollapsibleHeader (iOS)
//
// Created by 李宇鸿 on 2022/9/9.
//
import SwiftUI
struct Home: View {
var topEdge : CGFloat
// 最大高度
let maxHeight = UIScreen.main.bounds.height / 2.3
// 偏移量
@State var offset : CGFloat = 0
var body: some View {
ScrollView(.vertical,showsIndicators: false){
VStack(spacing:15){
// 顶部导航视图…
GeometryReader{proxy in
TopBar(topEdge: topEdge,offset: $offset,maxHeight:maxHeight)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
// 粘性的影响
.frame(height:getHeaderHeight(), alignment: .bottom)
.background(Color("TopBar"),in: CustomCorner(corners: [.bottomRight], radius: getCornerRadius())
)
.overlay(
// 顶部导航视图…
HStack(spacing:15){
Button {
} label: {
Image(systemName: "xmark")
.font(.body.bold())
}
Image("Pic")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 35, height: 35)
.clipShape(Circle())
.opacity(topBarTitleOpacity())
Text("iJustine")
.fontWeight(.bold)
.foregroundColor(.white)
.opacity(topBarTitleOpacity())
Spacer()
Button {
} label: {
Image(systemName: "line.3.horizontal.decrease")
.font(.body.bold())
}
}
.padding(.horizontal)
// 最大高度
.frame(height:80)
.foregroundColor(.white)
.padding(.top,topEdge)
,alignment: .top
)
}
.frame(height:maxHeight)
// 固定在顶部……
.offset(y:-offset)
.zIndex(1)
VStack(spacing:15){
ForEach(allMessages){message in
// 视图View
MessageCardView(message: message)
}
}
.padding()
.zIndex(0)
}
.modifier(OffsetModifier(offset: $offset))
}
// setting coordinate space...
// 设置坐标空间……
.coordinateSpace(name: "SCROLL")
}
func getHeaderHeight() -> CGFloat{
let topHeight = maxHeight + offset
// 80是固定的顶部导航栏高度…
//因为我们包括了顶部安全区域,所以我们也需要包括它…
return topHeight > (80 + topEdge) ? topHeight : (80 + topEdge)
}
func getCornerRadius()->CGFloat{
let progress = -offset / (maxHeight - (80 + topEdge) )
let value = 1 - progress
let radius = value * 50
return offset < 0 ? radius : 50
}
func topBarTitleOpacity()->CGFloat {
//开始后主要内容消失....
//我们需要消除70从偏移....
//开始…
let progress = -(offset + 60) / (maxHeight - (80 + topEdge))
// let opacity = 1 - progress
return progress
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct TopBar : View {
var topEdge : CGFloat
@Binding var offset : CGFloat
var maxHeight : CGFloat
var body: some View {
VStack(alignment: .leading, spacing: 15) {
Image("Pic")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 80, height: 80)
.cornerRadius(10)
Text("iJustine")
.font(.largeTitle.bold())
Text("Justine Ezarik is an American YouTuber, host, author, She is best known as iJustine")
.fontWeight(.semibold)
.foregroundColor(Color.white.opacity(0.8))
.lineLimit(2)
}
.padding()
.padding(.bottom)
.opacity(getOpacity())
}
// 计算不透明度
func getOpacity() -> CGFloat {
// 一些随机量的时间,以可见的滚动....
let progress = -offset / 70
let opacity = 1 - progress
return offset < 0 ? opacity : 1
}
}
struct MessageCardView: View {
var message : Message
var body: some View{
HStack(spacing: 15){
Circle()
.fill(message.tintColor)
.frame(width: 50, height: 50)
.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)
}
}
}
CustomCorner - 主要是自定义圆角角落
//
// CustomCorner.swift
// CollapsibleHeader (iOS)
//
// Created by 李宇鸿 on 2022/9/9.
//
import SwiftUI
// 自定义圆角区域
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)
}
}
OffsetModifier - 偏移量修饰
用来监听偏移量的值
//
// OffsetModifier.swift
// CollapsibleHeader (iOS)
//
// Created by 李宇鸿 on 2022/9/9.
//
import SwiftUI
// 得到ScrollView抵消……
struct OffsetModifier : ViewModifier {
@Binding var offset : CGFloat
func body(content: Content) -> some View {
content
.overlay(
GeometryReader{proxy -> Color in
// 获取坐标空间的值叫做scroll…
let minY = proxy.frame(in: .named("SCROLL")).minY
// 测试滚动的范围打印
// print(minY)
DispatchQueue.main.async {
self.offset = minY
}
return Color.clear
}
,alignment: .top
)
}
}
Message - 模型
//
// Message.swift
// CollapsibleHeader (iOS)
//
// Created by 李宇鸿 on 2022/9/9.
//
import SwiftUI
// Message Model and Sample Data...
// 消息模型和样本数据…
struct Message: Identifiable{
var id = UUID().uuidString
var message: String
var userName: String
var tintColor: Color
}
var allMessages : [Message] = [
Message(message: "Can we go to the park.", userName: "iJustine", tintColor: .pink),
Message(message: "We can go down to the store with the dog. It is not too far away.", userName: "Jenna", tintColor: .red),
Message(message: "Can we go to the park.", userName: "Aaron", tintColor: .green),
Message(message: "We can go down to the store with the dog. It is not too far away.", userName: "Kaviya", tintColor: .yellow),
Message(message: "Can we go to the park.", userName: "Finch", tintColor: .orange),
Message(message: "Can we go to the park.", userName: "Jeff", tintColor: .pink),
Message(message: "Can we go to the park.", userName: "Melinda", tintColor: .red),
Message(message: "Can we go to the park.", userName: "Cook", tintColor: .green),
Message(message: "Can we go to the park.", userName: "Jimmy", tintColor: .yellow),
Message(message: "Can we go to the park.", userName: "Tom", tintColor: .orange),
Message(message: "Can we go to the park.", userName: "Ellen", tintColor: .pink),
Message(message: "Can we go to the park.", userName: "Julia", tintColor: .red),
Message(message: "Can we go to the park.", userName: "Ashley", tintColor: .green),
Message(message: "Can we go to the park.", userName: "Emily", tintColor: .yellow),
]