在这段代码中,我在Home()中使用了一个标记为"查看配置文件"的按钮,将自定义选项卡栏中的选定选项卡更改为配置文件().

这会起作用(将当前选项卡更改为Profile(),但不会产生.clipShape(BottomCurve(CurrentXValue:CurrentXValue)效果.如何确保这是响应性的(就像CurrentTab变量一样)?

以下是代码:

class AppViewModel: ObservableObject {
       
    @Published var currentTab: Tab = .Home

}


struct MainPage: View {
    @StateObject var appModel: AppViewModel = .init()
    @Namespace var animation

    var body: some View {
        VStack(spacing: 0) {
            TabView(selection: $appModel.currentTab) {
                Profile()
                    .tag(Tab.Profile)
                    .setUpTab()

                Home(appModel: appModel)
                    .tag(Tab.Home)
                    .setUpTab()

                Statistics()
                    .tag(Tab.Statistics)
                    .setUpTab()

            }
            .overlay(alignment: .bottom) {
                CustomTabBar(currentTab: $appModel.currentTab, animation: animation)
            }
        }
    }
}



struct Home: View {
    
    @ObservedObject var appModel: AppViewModel
    
    init(appModel: AppViewModel) {
            self.appModel = appModel
        }
        
    var body: some View {
        
        VStack {
            
    
            Button(action: {
                appModel.currentTab = .Profile
            }) {
                HStack {
                    Text("See profile")
                        .font(.headline)
                        .foregroundColor(.black)
                    Image(systemName: "chevron.right")
                        .foregroundColor(.black)
                }
            }
        }
    }
}

struct Profile: View {
    
    var body: some View {
        
        VStack {
            
            Text("Profile view")
            
        }
    }
}

struct Statistics: View {
    
    var body: some View {
        
        VStack {
            
            Text("Statistics view")
            
        }
    }
}


struct CustomTabBar: View {
    @Binding var currentTab: Tab
    var animation: Namespace.ID
    // Current Tab XValue...
    @State var currentXValue: CGFloat = 0
    var body: some View {
        HStack(spacing: 0){
            ForEach(Tab.allCases,id: \.rawValue){tab in
                TabButton(tab: tab)
                    .overlay {
                        Text(tab.rawValue)
                            .font(.system(size: 14, weight: .semibold))
                            .foregroundColor(.yellow)
                            .fixedSize(horizontal: true, vertical: false)
                            .offset(y: currentTab == tab ? 15 : 100)
                    }
            }
        }
        .padding(.top)
        // Preview wont show safeArea...
        .padding(.bottom,getSafeArea().bottom == 0 ? 15 : 10)
        .background{

            Color(.gray)
                .shadow(color: .black.opacity(0.08), radius: 5, x: 0, y: -5)
                .clipShape(BottomCurve(currentXValue: currentXValue))
                .ignoresSafeArea(.container, edges: .bottom)
        }
    }
    // TabButton...
    @ViewBuilder
    func TabButton(tab: Tab)->some View{
        // Since we need XAxis Value for Curve...
        GeometryReader{proxy in
            Button {
                withAnimation(.easeInOut){
                    currentTab = tab
                    // updating Value...
                    currentXValue = proxy.frame(in: .global).midX
                }
                
            } label: {
                // Moving Button up for current Tab...
//                Image(tab.rawValue)
                Image(systemName: "person.fill")
                    .resizable()
                    .renderingMode(.template)
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 25, height: 25)
                    .frame(maxWidth: .infinity)
                    .foregroundColor(currentTab == tab ? .white : .blue.opacity(0.8))
                    .padding(currentTab == tab ? 15 : 0)
                    .background(
                        ZStack{
                            if currentTab == tab{
                                Circle()
                                    .fill(.green)
                                    .shadow(color: .black.opacity(0.1), radius: 8, x: 5, y: 5)
                                    .matchedGeometryEffect(id: "TAB", in: animation)
                            }
                        }
                    )
                    .contentShape(Rectangle())
                    .offset(y: currentTab == tab ? -50 : 0)
            }
            // Setting intial Curve Position...
            .onAppear {
                
                if tab == Tab.allCases.first && currentXValue == 0{
                    
                    currentXValue = proxy.frame(in: .global).midX
                }
            }
        }
        .frame(height: 30)
    }
}

enum Tab: String,CaseIterable{
    case Profile = "Profile"
    case Home = "Home"
    case Statistics = "Statistics"
}


extension View{
    func getSafeArea()->UIEdgeInsets{
        guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else{
            return .zero
        }
        
        guard let safeArea = screen.windows.first?.safeAreaInsets else{
            return .zero
        }
        
        return safeArea
    }
}


extension View{
    @ViewBuilder
    func setUpTab()->some View{
        self
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background{
                Color("StriderBG")
                    .ignoresSafeArea()
            }
    }
}

// Tab Bar Curve...
struct BottomCurve: Shape {

    var currentXValue: CGFloat
    
    // Animating Path...
    // it wont work on previews....
    var animatableData: CGFloat{
        get{currentXValue}
        set{currentXValue = newValue}
    }
    
    func path(in rect: CGRect) -> Path {
        
        return Path{path in
            
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: rect.width, y: 0))
            path.addLine(to: CGPoint(x: rect.width, y: rect.height))
            path.addLine(to: CGPoint(x: 0, y: rect.height))
            
            // mid will be current XValue..
            let mid = currentXValue
            path.move(to: CGPoint(x: mid - 50, y: 0))
            
            let to1 = CGPoint(x: mid, y: 35)
            let control1 = CGPoint(x: mid - 25, y: 0)
            let control2 = CGPoint(x: mid - 25, y: 35)
            
            path.addCurve(to: to1, control1: control1, control2: control2)
            
            let to2 = CGPoint(x: mid + 50, y: 0)
            let control3 = CGPoint(x: mid + 25, y: 35)
            let control4 = CGPoint(x: mid + 25, y: 0)
            
            path.addCurve(to: to2, control1: control3, control2: control4)
        }
    }
}

正如您在运行此代码时所看到的,当点击Home()中的按钮时,将显示Profile(),但BottomCurve效果不会响应.

推荐答案

下面的代码进行了修改,以固定曲线开始位置和曲线动画.无论是在第一次启动时还是在点击"查看个人资料"按钮时,都存在动画问题.

如果我错过了什么,请告诉我:

import SwiftUI

class AppViewModel: ObservableObject {
    @Published var currentTab: Tab = .Home
    @Published var currentIndex: Int = 1
}
struct MainPage: View {
    @StateObject var appModel: AppViewModel = .init()
    @Namespace var animation

    var body: some View {
        VStack(spacing: 0) {
            TabView(selection: $appModel.currentTab) { // Updated line
                Profile()
                    .tag(Tab.Profile)
                    .setUpTab()

                Home(appModel: appModel)
                    .tag(Tab.Home)
                    .setUpTab()

                Statistics()
                    .tag(Tab.Statistics)
                    .setUpTab()
            }
            .overlay(alignment: .bottom) {
                CustomTabBar(currentTab: $appModel.currentTab, animation: animation)
            }
        }
    }
}

struct Home: View {
    @ObservedObject var appModel: AppViewModel
    @Namespace var animation

    init(appModel: AppViewModel) {
        self.appModel = appModel
    }

    var body: some View {
        VStack {
            Button(action: {
                withAnimation(.easeInOut) { // Add animation
                    appModel.currentTab = .Profile
                    appModel.currentIndex = 0
                }
            }) {
                HStack {
                    Text("See profile")
                        .font(.headline)
                        .foregroundColor(.yellow)
                    Image(systemName: "chevron.right")
                        .foregroundColor(.black)
                        .matchedGeometryEffect(id: "Button", in: animation)
                }
            }
        }
    }
}

struct Profile: View {
    var body: some View {
        VStack {
            Text("Profile view")
        }
    }
}

struct Statistics: View {
    var body: some View {
        VStack {
            Text("Statistics view")
        }
    }
}

struct CustomTabBar: View {
    @Binding var currentTab: Tab
    var animation: Namespace.ID

    var body: some View {
        HStack(spacing: 0) {
            ForEach(Tab.allCases, id: \.rawValue) { tab in
                TabButton(tab: tab, currentTab: $currentTab)
                    .overlay {
                        Text(tab.rawValue)
                            .font(.system(size: 14, weight: .semibold))
                            .foregroundColor(.yellow)
                            .fixedSize(horizontal: true, vertical: false)
                            .offset(y: currentTab == tab ? 15 : 100)
                    }
            }
        }
        .padding(.top)
        .padding(.bottom, getSafeArea().bottom == 0 ? 15 : 10)
        .background {
            Color(.gray)
                .shadow(color: .black.opacity(0.08), radius: 5, x: 0, y: -5)
                .clipShape(BottomCurve(currentXValue: currentXValue(forTab: currentTab)))
                .ignoresSafeArea(.container, edges: .bottom)
        }
    }

    func currentXValue(forTab tab: Tab) -> CGFloat {
        let index = Tab.allCases.firstIndex(of: tab)!
        let tabWidth = UIScreen.main.bounds.width / CGFloat(Tab.allCases.count)
        return (CGFloat(index) * tabWidth) + (tabWidth / 2)
    }

    @ViewBuilder
    func TabButton(tab: Tab, currentTab: Binding<Tab>) -> some View {
        GeometryReader { proxy in
            Button {
                withAnimation(.easeInOut) {
                    currentTab.wrappedValue = tab
                }
            } label: {
                Image(systemName: "person.fill")
                    .resizable()
                    .renderingMode(.template)
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 25, height: 25)
                    .frame(maxWidth: .infinity)
                    .foregroundColor(currentTab.wrappedValue == tab ? .white : .blue.opacity(0.8))
                    .padding(currentTab.wrappedValue == tab ? 15 : 0)
                    .background(
                        ZStack {
                            if currentTab.wrappedValue == tab {
                                Circle()
                                    .fill(.green)
                                    .shadow(color: .black.opacity(0.1), radius: 8, x: 5, y: 5)
                                    .matchedGeometryEffect(id: "TAB", in: animation)
                            }
                        }
                    )
                    .contentShape(Rectangle())
                    .offset(y: currentTab.wrappedValue == tab ? -50 : 0)
            }
            .onAppear {
                if tab == Tab.allCases.first && currentXValue(forTab: tab) == 0 {
                    currentXValue(forTab: tab)
                }
            }
        }
        .frame(height: 30)
    }
}

enum Tab: String, CaseIterable {
    case Profile = "Profile"
    case Home = "Home"
    case Statistics = "Statistics"
}

extension View {
    func getSafeArea() -> EdgeInsets {
        guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
            return .init()
        }

        guard let safeArea = screen.windows.first?.safeAreaInsets else {
            return .init()
        }

        return EdgeInsets(top: safeArea.top, leading: safeArea.left, bottom: safeArea.bottom, trailing: safeArea.right)
    }
}

extension View {
    @ViewBuilder
    func setUpTab() -> some View {
        self
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background {
                Color("StriderBG")
                    .ignoresSafeArea()
            }
    }
}

struct BottomCurve: Shape {
    var currentXValue: CGFloat

    var animatableData: CGFloat {
        get { currentXValue }
        set { currentXValue = newValue }
    }

    func path(in rect: CGRect) -> Path {
        return Path { path in
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: rect.width, y: 0))
            path.addLine(to: CGPoint(x: rect.width, y: rect.height))
            path.addLine(to: CGPoint(x: 0, y: rect.height))

            let mid = currentXValue
            path.move(to: CGPoint(x: mid - 50, y: 0))

            let to1 = CGPoint(x: mid, y: 35)
            let control1 = CGPoint(x: mid - 25, y: 0)
            let control2 = CGPoint(x: mid - 25, y: 35)

            path.addCurve(to: to1, control1: control1, control2: control2)

            let to2 = CGPoint(x: mid + 50, y: 0)
            let control3 = CGPoint(x: mid + 25, y: 35)
            let control4 = CGPoint(x: mid + 25, y: 0)

            path.addCurve(to: to2, control1: control3, control2: control4)
        }
    }
}

Ios相关问答推荐

我如何确保用户继续他们在应用程序中停止的地方?

使用SWIFT传输表示时的条件文件表示格式

XcodeCloud生成失败,返回";ci_pre_xcodeBuild.sh是可执行文件,但退出时返回2个退出代码.";

SwiftData共享扩展阻止应用程序打开SQLite文件

在 Android 和 iOS 应用程序上下文中阻止后台线程有哪些缺点?

更改初始控制器无效

Task.cancel() 的 TaskPriority 的区别

如何在 Mac OS Ventura 中使用 Xcode 13

AppCenter 错误:/usr/bin/xcodebuild 失败,返回码:65

在 Swift 中映射 MySQL 数据

Text() 正在添加额外的前导尾随填充 SwiftUI

满足条件时 SwiftUI 动画背景 colored颜色 变化

Xcode 14 需要为 Pod Bundles Select 开发团队

将flutter android项目导入iOS

检测 iPhone/iPad/iPod touch 的 colored颜色 ?

故事板中的 colored颜色 与 UIColor 不匹配

为所有 UIImageViews 添加圆角

如何在 Swift 中将新单元格插入 UITableView

IOS - 从 UITextView 中删除所有填充

SwiftUI 应用生命周期 iOS14 AppDelegate 代码放哪里?