在 Viewbuilder 中使用变量 (Swift)

发布于 2025-01-15 13:40:47 字数 5727 浏览 3 评论 0原文

我是 swift 和 Viewbuilder 的新手,我正在尝试正确使用 Viewbuilder 中的变量。此代码的目的是使用动画揭示问题的答案。问题出现在一张卡片上,当点击卡片上的按钮时,就会显示答案。如果我使用一张卡,一切都很好,但是当我将多张卡放入滚动视图并点击按钮时,所有答案都会同时呈现。我相信这是因为我对所有卡片使用一个显示变量(因此它会同时展开所有卡片)。我需要为每张卡都有一个显示变量,但我正在努力实现这一点。我该怎么办呢?

我的视图的代码是:

struct Home: View {
// MARK: Animation Properties

@State var expandTheCard: Bool = false

@State var bottomLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)
@State var topLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)

// Avoiding Multitapping
@State var isfinished: Bool = false

var body: some View {
    
    NavigationView {
        
        ScrollView {
            
            LazyVStack(spacing: 200) {
                
                // MARK: Animated Liquid Transition Cards
                LiquidCard(title: "What Is Hello In French?", subTitle: "", detail: "Hello In French Is...", description: "Bonjour!"){
                    if isfinished{return}
                    isfinished = true
        
                    // Animating Lottie View with little Delay
                    DispatchQueue.main.asyncAfter(deadline: .now() + (expandTheCard ? 0 : 0.2)) {
                        // So that it will finish soon...
                        
                        bottomLiquidView.play(fromProgress: expandTheCard ? 0 : 0.45, toProgress: expandTheCard ? 0.6 : 0)
                        topLiquidView.play(fromProgress: expandTheCard ? 0 : 0.45, toProgress: expandTheCard ? 0.6 : 0){status in
                            isfinished = false
                        }
                    }
                    // Toggle Card
                    withAnimation(.interactiveSpring(response: 0.7, dampingFraction: 0.8, blendDuration: 0.8)){
                        expandTheCard.toggle()
                    }
                }
                .frame(maxHeight: .infinity)
            }
            
        }
        
    }
    
}

ViewBuilder 的代码是:

  @ViewBuilder
func LiquidCard(title: String,subTitle: String,detail: String,description: String,color: SwiftUI.Color = Color("Blue"),onExpand: @escaping ()->())->some View{

    ZStack{
        VStack(spacing: 20){
            Text(title)
                .font(.largeTitle.bold())
                .foregroundColor(.white)
            
            HStack(spacing: 10){
            
                Text(subTitle)
                    .fontWeight(.semibold)
                    .foregroundColor(.white)
            }
        }
        .padding()
        .frame(maxWidth: .infinity)
        .frame(height: expandTheCard ? 250 : 350)
        .background{
            GeometryReader{proxy in
                let size = proxy.size
                let scale = size.width / 1000
                
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                
                // To get Custom Color simply use Mask Technique
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                    .mask {
                        ResizableLottieView(lottieView: $bottomLiquidView)
                        // Scaling it to current Size
                            .scaleEffect(x: scale, y: scale, anchor: .leading)
                    }
                    .rotationEffect(.init(degrees: 180))
                    .offset(y: expandTheCard ? size.height / 1.43 : 0)
            }
        }
        // MARK: Expand Button
        .overlay(alignment: .bottom) {
            Button {
                onExpand()
            } label: {
             
                Image(systemName: "chevron.down")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 30.0, height: 30.0)
                    .font(.title3.bold())
                    .foregroundColor(color)
                    .padding(30)
                    .background(.white,in: RoundedRectangle(cornerRadius: 20, style: .continuous))
                // Shadows
                    .shadow(color: .black.opacity(0.15), radius: 5, x: 5, y: 5)
                    .shadow(color: .black.opacity(0.15), radius: 5, x: -5, y: -5)
            }
            .padding(.bottom,-25)
        }
        .zIndex(1)
        
        // MARK: Expanded Card
        
        VStack(spacing: 20){
            Text(detail)
                .font(.largeTitle.bold())
            
            Text(description)
                .font(.title3)
                .lineLimit(3)
                .padding(.horizontal)
                .multilineTextAlignment(.center)
        }
        .foregroundColor(.white)
        .padding(.vertical,40)
        .padding(.horizontal)
        .frame(maxWidth: .infinity)
        .background{
            GeometryReader{proxy in
                let size = proxy.size
                let scale = size.width / 1000
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                
                // To get Custom Color simply use Mask Technique
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                    .mask {
                        ResizableLottieView(lottieView: $topLiquidView)
                        // Scaling it to current Size
                            .scaleEffect(x: scale, y: scale, anchor: .leading)
                    }
                    .offset(y: expandTheCard ? -size.height / 1.2 : -size.height / 1.4)
            }
        }
        .zIndex(0)
        .offset(y: expandTheCard ? 280 : 0)
    }
    .offset(y: expandTheCard ? -120 : 0)
}

I'm new to swift and Viewbuilder and I am trying to use variables in the Viewbuilder correctly. The objective of this code is to reveal the answer to a question using an animation. The question is presented on a card and when the button on the card is tapped the answer is revealed. It all works well if I use one card however when I put multiple cards in a scrollView and tap the button, all of the answers are presented at the same time. I believe this is because I am using one show variable for all the cards (therefore it expands all the cards when at once). I need to have a show variable for each card but am struggling to implement this. How would I go about this?

The code for my View is:

struct Home: View {
// MARK: Animation Properties

@State var expandTheCard: Bool = false

@State var bottomLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)
@State var topLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)

// Avoiding Multitapping
@State var isfinished: Bool = false

var body: some View {
    
    NavigationView {
        
        ScrollView {
            
            LazyVStack(spacing: 200) {
                
                // MARK: Animated Liquid Transition Cards
                LiquidCard(title: "What Is Hello In French?", subTitle: "", detail: "Hello In French Is...", description: "Bonjour!"){
                    if isfinished{return}
                    isfinished = true
        
                    // Animating Lottie View with little Delay
                    DispatchQueue.main.asyncAfter(deadline: .now() + (expandTheCard ? 0 : 0.2)) {
                        // So that it will finish soon...
                        
                        bottomLiquidView.play(fromProgress: expandTheCard ? 0 : 0.45, toProgress: expandTheCard ? 0.6 : 0)
                        topLiquidView.play(fromProgress: expandTheCard ? 0 : 0.45, toProgress: expandTheCard ? 0.6 : 0){status in
                            isfinished = false
                        }
                    }
                    // Toggle Card
                    withAnimation(.interactiveSpring(response: 0.7, dampingFraction: 0.8, blendDuration: 0.8)){
                        expandTheCard.toggle()
                    }
                }
                .frame(maxHeight: .infinity)
            }
            
        }
        
    }
    
}

The code for the ViewBuilder is:

  @ViewBuilder
func LiquidCard(title: String,subTitle: String,detail: String,description: String,color: SwiftUI.Color = Color("Blue"),onExpand: @escaping ()->())->some View{

    ZStack{
        VStack(spacing: 20){
            Text(title)
                .font(.largeTitle.bold())
                .foregroundColor(.white)
            
            HStack(spacing: 10){
            
                Text(subTitle)
                    .fontWeight(.semibold)
                    .foregroundColor(.white)
            }
        }
        .padding()
        .frame(maxWidth: .infinity)
        .frame(height: expandTheCard ? 250 : 350)
        .background{
            GeometryReader{proxy in
                let size = proxy.size
                let scale = size.width / 1000
                
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                
                // To get Custom Color simply use Mask Technique
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                    .mask {
                        ResizableLottieView(lottieView: $bottomLiquidView)
                        // Scaling it to current Size
                            .scaleEffect(x: scale, y: scale, anchor: .leading)
                    }
                    .rotationEffect(.init(degrees: 180))
                    .offset(y: expandTheCard ? size.height / 1.43 : 0)
            }
        }
        // MARK: Expand Button
        .overlay(alignment: .bottom) {
            Button {
                onExpand()
            } label: {
             
                Image(systemName: "chevron.down")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: 30.0, height: 30.0)
                    .font(.title3.bold())
                    .foregroundColor(color)
                    .padding(30)
                    .background(.white,in: RoundedRectangle(cornerRadius: 20, style: .continuous))
                // Shadows
                    .shadow(color: .black.opacity(0.15), radius: 5, x: 5, y: 5)
                    .shadow(color: .black.opacity(0.15), radius: 5, x: -5, y: -5)
            }
            .padding(.bottom,-25)
        }
        .zIndex(1)
        
        // MARK: Expanded Card
        
        VStack(spacing: 20){
            Text(detail)
                .font(.largeTitle.bold())
            
            Text(description)
                .font(.title3)
                .lineLimit(3)
                .padding(.horizontal)
                .multilineTextAlignment(.center)
        }
        .foregroundColor(.white)
        .padding(.vertical,40)
        .padding(.horizontal)
        .frame(maxWidth: .infinity)
        .background{
            GeometryReader{proxy in
                let size = proxy.size
                let scale = size.width / 1000
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                
                // To get Custom Color simply use Mask Technique
                RoundedRectangle(cornerRadius: 35, style: .continuous)
                    .fill(color)
                    .mask {
                        ResizableLottieView(lottieView: $topLiquidView)
                        // Scaling it to current Size
                            .scaleEffect(x: scale, y: scale, anchor: .leading)
                    }
                    .offset(y: expandTheCard ? -size.height / 1.2 : -size.height / 1.4)
            }
        }
        .zIndex(0)
        .offset(y: expandTheCard ? 280 : 0)
    }
    .offset(y: expandTheCard ? -120 : 0)
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

装纯掩盖桑 2025-01-22 13:40:47

您对问题的评估是正确的:您只有一个变量来控制 LiquidCard 的所有实例。您应该有一个仅特定于 LiquidCardexpandTheCard 变量,而不连接到 Home 视图。

要实现此目的,一个好方法是将 LiquidCard 定义为新视图,而不是 @ViewBuilder func。顺便说一句,该名称已经是初始大小写,不应该用于函数名称。

以下是您应该如何更改视图(我只会在这里提到更改的部分):

  1. Home 视图中删除 expandTheCard
struct Home: View {
// MARK: Animation Properties

// Do not use this here: @State var expandTheCard: Bool = false

@State var bottomLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)
@State var topLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)

...
  1. 替换 func具有View类型的struct,其中包含@State var ExpandTheCard
import SwiftUI

struct LiquidCard: View {
    let title: String
    let subTitle: String,
    let detail: String,
    let description: String,
    let color: Color = .blue
    let onExpand: ()->()   // Or ()->Void, depending on your code

    // Here is where you need the variable, one for each LiquidCard
    @State var expandTheCard = false

    var body: some View {
        ZStack{
            VStack(spacing: 20){

...
  1. 按下按钮时展开卡片:
...
        // MARK: Expand Button
        .overlay(alignment: .bottom) {
            Button {
                expandTheCard = true    // Add this here or after onExpand(), depending on your code
                onExpand()
            } label: {
             
                Image(systemName: "chevron.down")

...
  1. 你的变量中没有这样的变量Home 不再查看,删除切换:
...
                    // Toggle Card
                    // You do not need this anymore
                    // withAnimation(.interactiveSpring(response: 0.7, dampingFraction: 0.8, blendDuration: 0.8)){
                    //    expandTheCard.toggle()
                    // }

...

You are right in the assessment of the problem: you have only one variable that is governing all instances of LiquidCard. You should have an expandTheCard variable that is specific only to LiquidCard, not connected to the Home view.

To achieve this, a good way is to define LiquidCard as a new view, not as a @ViewBuilder func. By the way, the name is already in initial cap, which shouldn't be used for function names.

Here's how you should change your views (I will only mention here the parts that change):

  1. Remove expandTheCard from the Home view:
struct Home: View {
// MARK: Animation Properties

// Do not use this here: @State var expandTheCard: Bool = false

@State var bottomLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)
@State var topLiquidView: AnimationView = AnimationView(name: "LiquidWave", bundle: .main)

...
  1. Replace the func with a struct of type View that contains the @State var expandTheCard:
import SwiftUI

struct LiquidCard: View {
    let title: String
    let subTitle: String,
    let detail: String,
    let description: String,
    let color: Color = .blue
    let onExpand: ()->()   // Or ()->Void, depending on your code

    // Here is where you need the variable, one for each LiquidCard
    @State var expandTheCard = false

    var body: some View {
        ZStack{
            VStack(spacing: 20){

...
  1. Expand the card when you push the Button:
...
        // MARK: Expand Button
        .overlay(alignment: .bottom) {
            Button {
                expandTheCard = true    // Add this here or after onExpand(), depending on your code
                onExpand()
            } label: {
             
                Image(systemName: "chevron.down")

...
  1. You have no such variable in the Home view anymore, delete the toggle:
...
                    // Toggle Card
                    // You do not need this anymore
                    // withAnimation(.interactiveSpring(response: 0.7, dampingFraction: 0.8, blendDuration: 0.8)){
                    //    expandTheCard.toggle()
                    // }

...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文