ios 如何从单独的视图更改修改器的值?

8qgya5xd  于 2023-06-25  发布在  iOS
关注(0)|答案(1)|浏览(93)

在这段代码中,我使用Home()中的一个标记为“seeprofile”的按钮,将自定义选项卡栏中的选定选项卡更改为Profile()。
这将起作用(将当前选项卡更改为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效果没有响应。

lnvxswe2

lnvxswe21#

下面的代码修改了曲线开始位置和曲线动画。首次启动和点击“查看配置文件”按钮时都存在动画问题。
如果我错过了什么,请告诉我:

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)
        }
    }
}

相关问题