SwiftUI模块系列 - 已更新27篇SwiftUI项目 - 已更新2个项目往期Demo源码下载

技术: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
  • ContentView - 主窗口
  • Home - 主页
  • CustomCorner - `主要是自定义圆角角落`
  • OffsetModifier - `偏移量修饰`
  • Message - 模型


概述

使用SwiftUI创建时尚的可折叠动画标题

详细

一、运行效果

swift获取导航栏高度_iOS

二、项目结构图

swift获取导航栏高度_swiftui_02

三、程序实现 - 过程

思路:

  1. 搭建导航整体拉伸模块
  2. 消息模块
  3. 进行滚动 大于或者小于导航高度 处理 展示拉伸或者隐藏导航头像标题模块
1.创建一个项目命名为 CollapsibleHeader

swift获取导航栏高度_滚动视图_03


swift获取导航栏高度_swiftui_04

1.1.引入资源文件和颜色

颜色
TopBar #451AF5 头像图片1张

swift获取导航栏高度_可折叠_05

2. 创建一个虚拟文件New Group 命名为 View

swift获取导航栏高度_可折叠_06


swift获取导航栏高度_swift获取导航栏高度_07

3. 创建一个虚拟文件New Group 命名为 Model

swift获取导航栏高度_可折叠_06


swift获取导航栏高度_iOS_09

4. 创建一个文件New File 选择SwiftUI View类型 命名为Home

swift获取导航栏高度_可折叠_10


swift获取导航栏高度_可折叠_11

swift获取导航栏高度_swiftui_12

5. 创建一个文件New File 选择SwiftUI View类型 命名为CustomCorner

主要是: 用于设置自定义圆角区域

swift获取导航栏高度_可折叠_10


swift获取导航栏高度_swift获取导航栏高度_14


swift获取导航栏高度_可折叠_15

swift获取导航栏高度_swift获取导航栏高度_16

6. 创建一个虚拟文件New Group 命名为 Modifiers

swift获取导航栏高度_可折叠_06


swift获取导航栏高度_滚动视图_18

7. 创建一个文件New File 选择SwiftUI View类型 命名为OffsetModifier

swift获取导航栏高度_可折叠_10


swift获取导航栏高度_swift获取导航栏高度_14

swift获取导航栏高度_swiftui_21


swift获取导航栏高度_可折叠_22

8. 创建一个文件New File 选择SwiftUI View类型 命名为Message 并且继承于Identifiable 作为模型

swift获取导航栏高度_可折叠_10


swift获取导航栏高度_swift获取导航栏高度_14

swift获取导航栏高度_swiftui_21

swift获取导航栏高度_可折叠_26

swift获取导航栏高度_滚动视图_27

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 - 主页

思路

  1. 搭建导航整体拉伸模块
  2. 消息模块
  3. 进行滚动 大于或者小于导航高度 处理 展示拉伸或者隐藏导航头像标题模块
//
//  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),
]