I'm trying to solve the following problem.
I have a GridStack view which generates an array of views. See below:
struct GridStack<Content: View> : View {
let rows :Int
let columns : Int
let content : (Int, Int) -> Content
var body: some View {
VStack{
ForEach(0 ..< rows){ row in
HStack{
ForEach(0 ..< columns){ column in
self.content(row, column)
}
}
}
}
}
}
I currently have this working code which sends the array's value into the integer variable 'upTo' in my main code:
GridStack(rows: 3, columns: 4){ rows, cols in
Button(action: {
upTo = rows * 4 + cols + 1
}, label: {
Text("Number \(rows * 4 + cols + 1)")
})
}
I would like to make this Text animate however if I add animations to the Text directly, every one of the texts moves on tapping where I would only like to animate the one that I am clicking.
I have tried to make the text into a Button view and pass it into the main button's label but apparently embedding buttons is a crime punishable by death.
Does anyone have any ideas? Thanks in advance!
I think two solutions are feasible. Please see my working examples below.
Would you like the applied effect to persist after clicking the button? In this case, I would recommend to create a custom view which stores the state.
struct GridStack<Content: View> : View {
let rows :Int
let columns : Int
let content : (Int, Int) -> Content
var body: some View {
VStack{
ForEach(0 ..< rows){ row in
HStack{
ForEach(0 ..< columns){ column in
self.content(row, column)
}
}
}
}
}
}
struct MyCustomView: View {
let upTo: Int
let callback: () -> Void
@State var animate = false
var body: some View {
Button(action: {
self.callback()
self.animate.toggle()
}) {
Text("Number \(self.upTo)")
.background(self.animate ? Color.red : Color.white)
.animation(.linear(duration: 1.0))
}
}
}
struct ContentView2: View {
@State var upTo: Int = 0
var body: some View {
VStack {
Text("\(self.upTo)")
GridStack(rows: 3, columns: 4) { rows, cols in
MyCustomView(upTo: rows * 4 + cols + 1, callback: {
self.upTo = rows * 4 + cols + 1
})
}
}
}
}
struct ContentView2_Previews: PreviewProvider {
static var previews: some View {
ContentView2()
}
}
Otherwise a custom button style will do. In this case you are able to access the configuration's isPressed
property.
struct GridStack<Content: View> : View {
let rows :Int
let columns : Int
let content : (Int, Int) -> Content
var body: some View {
VStack{
ForEach(0 ..< rows){ row in
HStack{
ForEach(0 ..< columns){ column in
self.content(row, column)
}
}
}
}
}
}
struct MyCustomButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(configuration.isPressed ? Color.red : Color.white)
.animation(.linear(duration: 1.0))
}
}
struct ContentView2: View {
@State var upTo: Int = 0
var body: some View {
VStack {
Text("\(self.upTo)")
GridStack(rows: 3, columns: 4) { rows, cols in
Button(action: {
self.upTo = rows * 4 + cols + 1
}) {
Text("Number \(rows * 4 + cols + 1)")
}
.buttonStyle(MyCustomButtonStyle())
}
}
}
}
struct ContentView2_Previews: PreviewProvider {
static var previews: some View {
ContentView2()
}
}
Amazing! Thanks again Nikolai. I used the first example as the second is a bit beyond me. To be honest I don't really understand what role the callback variable plays? Is it a variable? And is that what allows ContentView2 to read the value chosen when a button is pressed?
You're welcome! The button is wrapped inside the
MyCustomView
view. Your goal is to perform an action by clicking on the button. But in fact that the integer stateupTo
is not accessible from withinMyCustomView
, a closure which "describes" what to do is passed fromContentView2
. And finally, this closure is called by the button's action closure. Read more about closures here.Ok I'll practice my closures! It's a bit desperate, but could you have a look at my other question on my profile regarding element wise multiplication of some arrays? Something about my calculating the arrays is turning them into an Any operand which means I can't multiply elements of the array.