feat: 设置页面动效
This commit is contained in:
parent
f7eb4c0f51
commit
35962c644e
Binary file not shown.
@ -1,228 +1,194 @@
|
||||
import SwiftUI
|
||||
|
||||
// 自定义从左向右的过渡动画
|
||||
// MARK: - 自定义过渡动画
|
||||
extension AnyTransition {
|
||||
/// 创建从左向右的滑动过渡动画
|
||||
static var slideFromLeading: AnyTransition {
|
||||
.asymmetric(
|
||||
insertion: .move(edge: .trailing).combined(with: .opacity),
|
||||
removal: .move(edge: .leading).combined(with: .opacity)
|
||||
insertion: .move(edge: .trailing).combined(with: .opacity), // 从右侧滑入
|
||||
removal: .move(edge: .leading).combined(with: .opacity) // 向左侧滑出
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 定义路由
|
||||
// MARK: - 路由定义
|
||||
enum Route: Hashable {
|
||||
case settings
|
||||
case settings // 设置页面路由
|
||||
}
|
||||
|
||||
// MARK: - 主视图
|
||||
struct ContentView: View {
|
||||
@State private var showModal = false
|
||||
@State private var showSettings = false
|
||||
@State private var navigationPath = NavigationPath()
|
||||
@State private var contentOffset: CGFloat = 0
|
||||
// MARK: - 状态属性
|
||||
@State private var showModal = false // 控制用户资料弹窗显示
|
||||
@State private var showSettings = false // 控制设置页面显示
|
||||
@State private var navigationPath = NavigationPath() // 导航路径
|
||||
@State private var contentOffset: CGFloat = 0 // 内容偏移量
|
||||
|
||||
var body: some View {
|
||||
// MARK: - 主体视图
|
||||
var body: some View {
|
||||
NavigationStack(path: $navigationPath) {
|
||||
// 添加动画修饰符到 NavigationStack
|
||||
// 调试信息
|
||||
let _ = Self._printChanges()
|
||||
let _ = print("Navigation path changed: \(navigationPath)")
|
||||
let _ = print("导航路径已更新: \(navigationPath)")
|
||||
|
||||
// 主内容区域
|
||||
ZStack {
|
||||
// 主内容视图
|
||||
VStack {
|
||||
VStack(spacing: 20) {
|
||||
// This spacer ensures content stays below the status bar
|
||||
// 状态栏占位
|
||||
Spacer().frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
|
||||
// 顶部栏
|
||||
|
||||
// 顶部导航栏 - 左侧设置按钮
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
||||
showModal = true
|
||||
}
|
||||
}) {
|
||||
// 设置按钮
|
||||
Button(action: showUserProfile) {
|
||||
Image(systemName: "gearshape")
|
||||
.font(.title2)
|
||||
.padding()
|
||||
}
|
||||
Spacer() // 将按钮推到左侧
|
||||
}
|
||||
Spacer()
|
||||
|
||||
// 内容列表
|
||||
List {
|
||||
Section(header: Text("我的收藏")) {
|
||||
ForEach(1...5, id: \.self) { item in
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("最近活动")) {
|
||||
ForEach(6...10, id: \.self) { item in
|
||||
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)
|
||||
|
||||
}
|
||||
.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)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(.systemBackground))
|
||||
// 当显示弹窗时,主内容向右偏移
|
||||
.offset(x: showModal ? UIScreen.main.bounds.width * 0.8 : 0)
|
||||
// 页面内元素的动画
|
||||
.animation(.spring(response: 0.6, dampingFraction: 0.8), value: showModal)
|
||||
.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) {
|
||||
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(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 {
|
||||
SettingsView(isPresented: $showSettings)
|
||||
.transition(.move(edge: .leading))
|
||||
.zIndex(1) // Ensure it's above other content
|
||||
.onAppear {
|
||||
// Reset the navigation path when settings appear
|
||||
navigationPath.removeLast(navigationPath.count)
|
||||
}
|
||||
// 用户资料弹窗
|
||||
SlideInModal(
|
||||
isPresented: $showModal,
|
||||
onDismiss: hideUserProfile
|
||||
) {
|
||||
UserProfileModal(
|
||||
showModal: $showModal,
|
||||
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 {
|
||||
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) {
|
||||
// 遮罩背景
|
||||
if isPresented {
|
||||
Color.black
|
||||
.opacity(0.5)
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.transition(.opacity)
|
||||
.zIndex(1)
|
||||
@ -49,7 +49,7 @@ struct SlideInModal<Content: View>: View {
|
||||
.animation(animation, value: isPresented)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||
.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
|
||||
|
||||
/// 设置页面视图
|
||||
struct SettingsView: View {
|
||||
// MARK: - 属性
|
||||
|
||||
/// 环境变量 - 用于dismiss视图
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var isAppeared = false
|
||||
|
||||
/// 状态 - 控制视图显示/隐藏
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
// Animation configuration
|
||||
// MARK: - 动画配置
|
||||
|
||||
/// 动画配置
|
||||
private let animation = Animation.spring(
|
||||
response: 0.8,
|
||||
dampingFraction: 0.6,
|
||||
blendDuration: 0.8
|
||||
response: 0.6, // 响应时间
|
||||
dampingFraction: 0.9, // 阻尼系数
|
||||
blendDuration: 0.8 // 混合时间
|
||||
)
|
||||
|
||||
// MARK: - 主体视图
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// Custom navigation bar
|
||||
// 自定义导航栏
|
||||
HStack {
|
||||
// 返回按钮
|
||||
Button(action: {
|
||||
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
||||
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) {
|
||||
withAnimation(animation) {
|
||||
isPresented = false
|
||||
}
|
||||
}) {
|
||||
@ -155,47 +35,121 @@ struct SettingsView: View {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.foregroundColor(.blue)
|
||||
Text("Back")
|
||||
Text("返回")
|
||||
.font(.system(size: 17, weight: .regular))
|
||||
.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 {
|
||||
NavigationView {
|
||||
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