feat: 设置页面动效
This commit is contained in:
parent
f7eb4c0f51
commit
35962c644e
Binary file not shown.
@ -1,228 +1,194 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// 自定义从左向右的过渡动画
|
// MARK: - 自定义过渡动画
|
||||||
extension AnyTransition {
|
extension AnyTransition {
|
||||||
|
/// 创建从左向右的滑动过渡动画
|
||||||
static var slideFromLeading: AnyTransition {
|
static var slideFromLeading: AnyTransition {
|
||||||
.asymmetric(
|
.asymmetric(
|
||||||
insertion: .move(edge: .trailing).combined(with: .opacity),
|
insertion: .move(edge: .trailing).combined(with: .opacity), // 从右侧滑入
|
||||||
removal: .move(edge: .leading).combined(with: .opacity)
|
removal: .move(edge: .leading).combined(with: .opacity) // 向左侧滑出
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 定义路由
|
// MARK: - 路由定义
|
||||||
enum Route: Hashable {
|
enum Route: Hashable {
|
||||||
case settings
|
case settings // 设置页面路由
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - 主视图
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State private var showModal = false
|
// MARK: - 状态属性
|
||||||
@State private var showSettings = false
|
@State private var showModal = false // 控制用户资料弹窗显示
|
||||||
@State private var navigationPath = NavigationPath()
|
@State private var showSettings = false // 控制设置页面显示
|
||||||
@State private var contentOffset: CGFloat = 0
|
@State private var navigationPath = NavigationPath() // 导航路径
|
||||||
|
@State private var contentOffset: CGFloat = 0 // 内容偏移量
|
||||||
|
|
||||||
var body: some View {
|
// MARK: - 主体视图
|
||||||
|
var body: some View {
|
||||||
NavigationStack(path: $navigationPath) {
|
NavigationStack(path: $navigationPath) {
|
||||||
// 添加动画修饰符到 NavigationStack
|
// 调试信息
|
||||||
let _ = Self._printChanges()
|
let _ = Self._printChanges()
|
||||||
let _ = print("Navigation path changed: \(navigationPath)")
|
let _ = print("导航路径已更新: \(navigationPath)")
|
||||||
|
|
||||||
|
// 主内容区域
|
||||||
ZStack {
|
ZStack {
|
||||||
|
// 主内容视图
|
||||||
VStack {
|
VStack {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
// This spacer ensures content stays below the status bar
|
// 状态栏占位
|
||||||
Spacer().frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
|
Spacer().frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
|
||||||
// 顶部栏
|
|
||||||
|
// 顶部导航栏 - 左侧设置按钮
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
// 设置按钮
|
||||||
Button(action: {
|
Button(action: showUserProfile) {
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
||||||
showModal = true
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Image(systemName: "gearshape")
|
Image(systemName: "gearshape")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
Spacer() // 将按钮推到左侧
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
.offset(x: showModal ? UIScreen.main.bounds.width * 0.35 : 0)
|
|
||||||
.animation(.spring(response: 0.5, dampingFraction: 0.8), value: showModal)
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
}
|
|
||||||
// 添加半透明遮罩层
|
|
||||||
if showModal {
|
|
||||||
Color.black.opacity(0.4)
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
||||||
showModal = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.transition(.opacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal with animation - will be pushed off-screen by SettingsView
|
|
||||||
SlideInModal(isPresented: $showModal, onDismiss: {
|
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
||||||
showModal = false
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
// Modal content
|
|
||||||
// Modal content with offset for SettingsView
|
|
||||||
VStack(spacing: 20) {
|
|
||||||
// 用户信息区域
|
|
||||||
HStack(alignment: .center, spacing: 16) {
|
|
||||||
// 头像
|
|
||||||
Image(systemName: "person.circle.fill")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fill)
|
|
||||||
.frame(width: 60, height: 60)
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
.clipShape(Circle())
|
|
||||||
|
|
||||||
// 姓名和ID
|
// 内容列表
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
List {
|
||||||
Text("用户名")
|
Section(header: Text("我的收藏")) {
|
||||||
.font(.headline)
|
ForEach(1...5, id: \.self) { item in
|
||||||
.foregroundColor(.primary)
|
HStack {
|
||||||
|
Image(systemName: "photo")
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.background(Color.blue.opacity(0.1))
|
||||||
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("项目 \(item)")
|
||||||
|
.font(.headline)
|
||||||
|
Text("这是第\(item)个项目的描述")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text("ID: 12345678")
|
Section(header: Text("最近活动")) {
|
||||||
.font(.subheadline)
|
ForEach(6...10, id: \.self) { item in
|
||||||
.foregroundColor(.secondary)
|
HStack {
|
||||||
|
Image(systemName: "clock")
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.background(Color.orange.opacity(0.1))
|
||||||
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("活动 \(item)")
|
||||||
|
.font(.headline)
|
||||||
|
Text("\(item)分钟前更新")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("查看")
|
||||||
|
.font(.caption)
|
||||||
|
.padding(6)
|
||||||
|
.background(Color.blue.opacity(0.1))
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.cornerRadius(4)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.listStyle(GroupedListStyle())
|
||||||
|
.padding(.top, 0)
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.padding(.top, 16)
|
.background(Color(.systemBackground))
|
||||||
|
// 当显示弹窗时,主内容向右偏移
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
.offset(x: showModal ? UIScreen.main.bounds.width * 0.8 : 0)
|
||||||
Text("会员等级")
|
// 页面内元素的动画
|
||||||
.font(.headline)
|
.animation(.spring(response: 0.6, dampingFraction: 0.8), value: showModal)
|
||||||
.foregroundColor(.primary)
|
.edgesIgnoringSafeArea(.all)
|
||||||
Text("会员时间")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
Text("会员中心")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding(16)
|
|
||||||
.background(Color(red: 0.92, green: 0.92, blue: 0.92))
|
|
||||||
.cornerRadius(10)
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
|
|
||||||
VStack(spacing: 12) {
|
|
||||||
// memories
|
|
||||||
Button(action: {
|
|
||||||
print("memories")
|
|
||||||
}) {
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
Image(systemName: "crown.fill")
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
|
|
||||||
Text("My Memories")
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.cornerRadius(10)
|
|
||||||
.contentShape(Rectangle()) // 使整个区域可点击
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
|
||||||
|
|
||||||
// Box
|
|
||||||
Button(action: {
|
|
||||||
print("Box")
|
|
||||||
}) {
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
Image(systemName: "clock.fill")
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
Text("My Bind Box")
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.cornerRadius(10)
|
|
||||||
.contentShape(Rectangle()) // 使整个区域可点击
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
|
||||||
|
|
||||||
// setting
|
|
||||||
Button(action: {
|
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
||||||
showSettings = true
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
Image(systemName: "person.circle.fill")
|
|
||||||
.foregroundColor(.purple)
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
Text("Setting")
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
.cornerRadius(10)
|
|
||||||
.contentShape(Rectangle()) // 使整个区域可点击
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
// 这里可以添加其他设置项
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply offset to the entire modal when SettingsView is shown
|
|
||||||
.offset(x: showSettings ? UIScreen.main.bounds.width : 0)
|
|
||||||
.animation(.spring(response: 0.5, dampingFraction: 0.8), value: showSettings)
|
|
||||||
|
|
||||||
ZStack {
|
|
||||||
// Semi-transparent overlay for settings
|
|
||||||
if showSettings {
|
|
||||||
Color.black.opacity(0.4)
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
.onTapGesture {
|
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
||||||
showSettings = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.transition(.opacity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full screen settings view with slide animation
|
// 用户资料弹窗
|
||||||
if showSettings {
|
SlideInModal(
|
||||||
SettingsView(isPresented: $showSettings)
|
isPresented: $showModal,
|
||||||
.transition(.move(edge: .leading))
|
onDismiss: hideUserProfile
|
||||||
.zIndex(1) // Ensure it's above other content
|
) {
|
||||||
.onAppear {
|
UserProfileModal(
|
||||||
// Reset the navigation path when settings appear
|
showModal: $showModal,
|
||||||
navigationPath.removeLast(navigationPath.count)
|
showSettings: $showSettings
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
// 当显示设置页面时,将弹窗向右移出屏幕
|
||||||
|
.offset(x: showSettings ? UIScreen.main.bounds.width : 0)
|
||||||
|
.animation(.spring(response: 0.6, dampingFraction: 0.8), value: showSettings)
|
||||||
|
|
||||||
|
// 设置页面遮罩层
|
||||||
|
ZStack {
|
||||||
|
// 半透明遮罩
|
||||||
|
if showSettings {
|
||||||
|
Color.black.opacity(0)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
.onTapGesture(perform: hideSettings)
|
||||||
|
.transition(.opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置页面
|
||||||
|
if showSettings {
|
||||||
|
SettingsView(isPresented: $showSettings)
|
||||||
|
.transition(.move(edge: .leading)) // 从左侧滑入
|
||||||
|
.zIndex(1) // 确保设置页面在其他内容之上
|
||||||
|
.onAppear(perform: resetNavigationPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.animation(.spring(response: 0.6, dampingFraction: 0.8), value: showSettings)
|
||||||
}
|
}
|
||||||
.animation(.spring(response: 0.5, dampingFraction: 0.8), value: showSettings)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// MARK: - 私有方法
|
||||||
}
|
|
||||||
|
/// 显示用户资料弹窗
|
||||||
|
private func showUserProfile() {
|
||||||
|
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
|
||||||
|
showModal.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 隐藏用户资料弹窗
|
||||||
|
private func hideUserProfile() {
|
||||||
|
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
|
||||||
|
showModal = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 隐藏设置页面
|
||||||
|
private func hideSettings() {
|
||||||
|
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
|
||||||
|
showSettings = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 重置导航路径
|
||||||
|
private func resetNavigationPath() {
|
||||||
|
navigationPath.removeLast(navigationPath.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 预览
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentView()
|
ContentView()
|
||||||
}
|
}
|
||||||
|
|||||||
22
wake/Extensions/ViewExtensions.swift
Normal file
22
wake/Extensions/ViewExtensions.swift
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// MARK: - 圆角扩展
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,8 +16,8 @@ struct SlideInModal<Content: View>: View {
|
|||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
// 遮罩背景
|
// 遮罩背景
|
||||||
if isPresented {
|
if isPresented {
|
||||||
Color.black
|
Color.clear
|
||||||
.opacity(0.5)
|
.contentShape(Rectangle())
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.zIndex(1)
|
.zIndex(1)
|
||||||
@ -49,7 +49,7 @@ struct SlideInModal<Content: View>: View {
|
|||||||
.animation(animation, value: isPresented)
|
.animation(animation, value: isPresented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
135
wake/View/Components/UserProfileModal.swift
Normal file
135
wake/View/Components/UserProfileModal.swift
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserProfileModal: View {
|
||||||
|
@Binding var showModal: Bool
|
||||||
|
@Binding var showSettings: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
// Modal content with transparent background
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
Spacer()
|
||||||
|
.frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
|
||||||
|
// 用户信息区域
|
||||||
|
HStack(alignment: .center, spacing: 16) {
|
||||||
|
// 头像
|
||||||
|
Image(systemName: "person.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.clipShape(Circle())
|
||||||
|
|
||||||
|
// 姓名和ID
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text("用户名")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Text("ID: 12345678")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.top, 16)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("会员等级")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("会员时间")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Text("会员中心")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(16)
|
||||||
|
.background(Color.clear)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
// memories
|
||||||
|
Button(action: {
|
||||||
|
print("memories")
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: "crown.fill")
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
|
||||||
|
Text("My Memories")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.cornerRadius(10)
|
||||||
|
.contentShape(Rectangle()) // 使整个区域可点击
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||||
|
|
||||||
|
// Box
|
||||||
|
Button(action: {
|
||||||
|
print("Box")
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: "clock.fill")
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
Text("My Bind Box")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.cornerRadius(10)
|
||||||
|
.contentShape(Rectangle()) // 使整个区域可点击
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||||
|
|
||||||
|
// setting
|
||||||
|
Button(action: {
|
||||||
|
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
||||||
|
showSettings = true
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: "person.circle.fill")
|
||||||
|
.foregroundColor(.purple)
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
Text("Setting")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color.clear)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
|
||||||
|
)
|
||||||
|
.contentShape(Rectangle()) // 使整个区域可点击
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle()) // 移除按钮默认样式
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(width: UIScreen.main.bounds.width * 0.8)
|
||||||
|
.background(Color(red: 0.87, green: 0.87, blue: 0.87))
|
||||||
|
.cornerRadius(20, corners: [.topRight, .bottomRight])
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
UserProfileModal(showModal: .constant(true), showSettings: .constant(false))
|
||||||
|
}
|
||||||
@ -1,153 +1,33 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
/// 设置页面视图
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
|
// MARK: - 属性
|
||||||
|
|
||||||
|
/// 环境变量 - 用于dismiss视图
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@State private var isAppeared = false
|
|
||||||
|
/// 状态 - 控制视图显示/隐藏
|
||||||
@Binding var isPresented: Bool
|
@Binding var isPresented: Bool
|
||||||
|
|
||||||
// Animation configuration
|
// MARK: - 动画配置
|
||||||
|
|
||||||
|
/// 动画配置
|
||||||
private let animation = Animation.spring(
|
private let animation = Animation.spring(
|
||||||
response: 0.8,
|
response: 0.6, // 响应时间
|
||||||
dampingFraction: 0.6,
|
dampingFraction: 0.9, // 阻尼系数
|
||||||
blendDuration: 0.8
|
blendDuration: 0.8 // 混合时间
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MARK: - 主体视图
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Custom navigation bar
|
// 自定义导航栏
|
||||||
HStack {
|
HStack {
|
||||||
|
// 返回按钮
|
||||||
Button(action: {
|
Button(action: {
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
withAnimation(animation) {
|
||||||
isPresented = false
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
HStack(spacing: 4) {
|
|
||||||
Image(systemName: "chevron.left")
|
|
||||||
.font(.system(size: 17, weight: .semibold))
|
|
||||||
Text("Back")
|
|
||||||
}
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Text("Settings")
|
|
||||||
.font(.headline)
|
|
||||||
.padding()
|
|
||||||
Spacer()
|
|
||||||
// Invisible view to balance the layout
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 44, height: 44)
|
|
||||||
}
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
|
|
||||||
// Settings content
|
|
||||||
List(0..<1) { _ in
|
|
||||||
// This empty section ensures proper spacing
|
|
||||||
Section {
|
|
||||||
EmptyView()
|
|
||||||
} header: {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
// Add an invisible section header to remove extra top padding
|
|
||||||
Section(header: EmptyView()) {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
// Account & Security
|
|
||||||
HStack {
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
Image(systemName: "person.crop.circle")
|
|
||||||
.font(.system(size: 24))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Text("Account & Security")
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
}
|
|
||||||
.listRowBackground(Color(.systemBackground))
|
|
||||||
|
|
||||||
// Permission Management
|
|
||||||
HStack {
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
Image(systemName: "lock.shield")
|
|
||||||
.font(.system(size: 24))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Text("Permission Management")
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
}
|
|
||||||
.listRowBackground(Color(.systemBackground))
|
|
||||||
|
|
||||||
// Support & Service
|
|
||||||
HStack {
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
Image(systemName: "questionmark.circle")
|
|
||||||
.font(.system(size: 24))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Text("Support & Service")
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
}
|
|
||||||
.listRowBackground(Color(.systemBackground))
|
|
||||||
|
|
||||||
// About Us
|
|
||||||
HStack {
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
Image(systemName: "info.circle")
|
|
||||||
.font(.system(size: 24))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Text("About Us")
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
Color.clear
|
|
||||||
.frame(width: 12, height: 24)
|
|
||||||
.background(Color(.systemBackground))
|
|
||||||
}
|
|
||||||
.listRowBackground(Color(.systemBackground))
|
|
||||||
}
|
|
||||||
.listStyle(GroupedListStyle())
|
|
||||||
.navigationTitle("Setting")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.background(Color(.systemGray6))
|
|
||||||
.environment(\.horizontalSizeClass, .regular)
|
|
||||||
.environment(\.defaultMinListRowHeight, 50)
|
|
||||||
.listRowInsets(EdgeInsets())
|
|
||||||
.onAppear {
|
|
||||||
// Remove extra separators below the list
|
|
||||||
UITableView.appearance().tableFooterView = UIView()
|
|
||||||
// Remove separator inset
|
|
||||||
UITableView.appearance().separatorInset = .zero
|
|
||||||
// Remove extra space at the top of the table view
|
|
||||||
UITableView.appearance().contentInset = .zero
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
|
||||||
Button(action: {
|
|
||||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
||||||
isAppeared = false
|
|
||||||
}
|
|
||||||
// Delay the dismiss to allow the animation to complete
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
||||||
isPresented = false
|
isPresented = false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@ -155,47 +35,121 @@ struct SettingsView: View {
|
|||||||
Image(systemName: "chevron.left")
|
Image(systemName: "chevron.left")
|
||||||
.font(.system(size: 16, weight: .medium))
|
.font(.system(size: 16, weight: .medium))
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
Text("Back")
|
Text("返回")
|
||||||
.font(.system(size: 17, weight: .regular))
|
.font(.system(size: 17, weight: .regular))
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
Text("设置")
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 用于平衡布局的透明视图
|
||||||
|
Color.clear
|
||||||
|
.frame(width: 44, height: 44)
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
|
||||||
|
// 设置项列表
|
||||||
|
List {
|
||||||
|
// 空Section用于调整间距
|
||||||
|
Section { EmptyView() }
|
||||||
|
|
||||||
|
// 账号与安全
|
||||||
|
settingRow(
|
||||||
|
icon: "person.crop.circle",
|
||||||
|
title: "账号与安全",
|
||||||
|
action: {}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 权限管理
|
||||||
|
settingRow(
|
||||||
|
icon: "lock.shield",
|
||||||
|
title: "权限管理",
|
||||||
|
action: {}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 支持与服务
|
||||||
|
settingRow(
|
||||||
|
icon: "questionmark.circle",
|
||||||
|
title: "支持与服务",
|
||||||
|
action: {}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 关于我们
|
||||||
|
settingRow(
|
||||||
|
icon: "info.circle",
|
||||||
|
title: "关于我们",
|
||||||
|
action: {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.listStyle(GroupedListStyle())
|
||||||
|
.listRowInsets(EdgeInsets())
|
||||||
}
|
}
|
||||||
.animation(animation, value: isAppeared)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
.environment(\.horizontalSizeClass, .regular)
|
||||||
|
.environment(\.defaultMinListRowHeight, 50)
|
||||||
|
.onAppear(perform: configureTableView)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// MARK: - 私有方法
|
||||||
|
|
||||||
|
/// 配置TableView外观
|
||||||
|
private func configureTableView() {
|
||||||
|
// 移除列表底部分隔线
|
||||||
|
UITableView.appearance().tableFooterView = UIView()
|
||||||
|
// 移除分隔线缩进
|
||||||
|
UITableView.appearance().separatorInset = .zero
|
||||||
|
// 移除列表顶部额外间距
|
||||||
|
UITableView.appearance().contentInset = .zero
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建设置项行视图
|
||||||
|
/// - Parameters:
|
||||||
|
/// - icon: 图标名称
|
||||||
|
/// - title: 标题
|
||||||
|
/// - action: 点击动作
|
||||||
|
/// - Returns: 返回设置项行视图
|
||||||
|
private func settingRow(icon: String, title: String, action: @escaping () -> Void) -> some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack {
|
||||||
|
// 左侧图标
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: 24))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.frame(width: 40)
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
Text(title)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 右侧箭头
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.listRowBackground(Color(.systemBackground))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Preview
|
// MARK: - 预览
|
||||||
#Preview {
|
#Preview {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
SettingsView(isPresented: .constant(true))
|
SettingsView(isPresented: .constant(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Subviews
|
|
||||||
struct AccountSecurityView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text("Account & Security")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PermissionManagementView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text("Permission Management")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SupportServiceView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text("Support & Service")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AboutUsView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text("About Us")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user