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)
}
You are right in the assessment of the problem: you have only one variable that is governing all instances of
LiquidCard
. You should have anexpandTheCard
variable that is specific only toLiquidCard
, not connected to theHome
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):
expandTheCard
from theHome
view:func
with astruct
of typeView
that contains the@State var expandTheCard
:Home
view anymore, delete the toggle: