0

I have a fairly simple view with a background image and a picker. I would like to change the background image based on the currently selected value. The code below works, but the animation for the image change is missing - any idea why?

@State private var pickerIndex = 0
@State private var currentBackgroundImage = "fitness-1"

var body: some View {
        ZStack {
            Image(currentBackgroundImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .transition(.opacity)
                .edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 20) {
                    
                    Picker(selection: $pickerIndex, label: Text("")) {
                        Image("img1")
                            .tag(0)
                        Image("img2")
                            .tag(1)
                    }
                    .onChange(of: genderPickerIndex, perform: { value in
                        if(value == 0) {
                            withAnimation {
                                currentBackgroundImage = "fitness-1"
                            }
                        } else {
                            withAnimation {
                                currentBackgroundImage = "fitness-2"
                            }
                        }
                    })
                    .frame(width: 100)
                    .pickerStyle(SegmentedPickerStyle())
            }
        }
    }

EDIT:

It seems like it has nothing to do with the Picker - I just replaced it with a button and the image change is still not animated. Am I missing some modifier on the image?

ZStack {
    Image(currentBackgroundImage)
        .resizable()
        .aspectRatio(contentMode: .fill)
        .transition(.opacity)
        .edgesIgnoringSafeArea(.all)
           
    Button(action: {
        withAnimation {
            currentBackgroundImage = "fitness-2"
        }
    }) {
        Text("Change Image")
    }
}
Tobilence
  • 115
  • 1
  • 6

2 Answers2

0

Your Picker selection is of type String, and your picker data is of type Text, so your onChange modifier isn’t invoked. Picker selection type and content type must be same, also explained by @Asperi in link Picker in SwiftUI works with one version of ForEach, but not the other - a bug, or expected behavior?. Instead you could use forEach and loop over your [string] names.

struct ContentViewsss:View{
    var images = ["ABC","CDF"]
    @State private var pickerValue = "ABC"
    @State private var currentBackgroundImage = "ABC"
    
    var body: some View {
        VStack {
            Image(currentBackgroundImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 150, height: 150)
                .clipShape(Circle())
                .edgesIgnoringSafeArea(.all)
                .transition(.opacity)
            
            Picker(selection: $pickerValue, label: Text("")) {
                ForEach(images,id:\.self) { imageName in
                    Text("\(imageName)")
                }
            }
            .onChange(of: pickerValue, perform: { value in
                
                self.changeBackgroundImage(value)
            })
            .frame(width: 200)
            .pickerStyle(SegmentedPickerStyle())
            
        }
    }
    
    func changeBackgroundImage(_ value: String) {
        switch value {
        case "ABC":
            withAnimation {
                currentBackgroundImage = "ABC"
            }//the app changes the image, but the "withAnimation" does not seem to do anything
        
        case "CDF":
            withAnimation {
                currentBackgroundImage = "CDF"
            }//the app changes the image, but the "withAnimation" does not seem to do anything
        
        default:
            currentBackgroundImage = "landing-page"
        }
        
    }
}

Or you could directly assign newValue inside onChange, instead of separate function.

struct ContentViewsss:View{
    var images = ["ABC","CDF"]
    @State private var pickerValue = "ABC"
    @State private var currentBackgroundImage = "ABC"
    
    var body: some View {
        VStack {
            Image(currentBackgroundImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 150, height: 150)
                .clipShape(Circle())
                .edgesIgnoringSafeArea(.all)
                .transition(.opacity)
            
            Picker(selection: $pickerValue, label: Text("")) {
                ForEach(images,id:\.self) { imageName in
                    Text("\(imageName)")
                }
            }
            .onChange(of: pickerValue, perform: { value in
           withAnimation {
                currentBackgroundImage = value
             } 
                //self.changeBackgroundImage(value)
            })
            .frame(width: 200)
            .pickerStyle(SegmentedPickerStyle())
            
        }
    }
}
Tushar Sharma
  • 2,839
  • 1
  • 16
  • 38
  • In my code I already had it with the ForEach.. I tried to make it simpler in StackOverflow and messed up the code. Sorry that's my bad! I now tried to assign the new value directly in the onChange. Again the image changes, but **without an animation**. Any idea why? – Tobilence Mar 05 '21 at 07:55
  • @Tobilence is the issue fixed? I can see animation happening in my code.I just added withAnimation block as well in onChange. – Tushar Sharma Mar 05 '21 at 07:59
  • No in my code it still does not work - give me a few minutes I'll update my post with my current code. – Tobilence Mar 05 '21 at 08:08
  • I edited it - I now use an index instead of the String value in the picker, but no animation is happening – Tobilence Mar 05 '21 at 08:16
  • @Tobilence what is genderPickerIndex? I changed it to pickerIndex and it’s working fine. – Tushar Sharma Mar 05 '21 at 08:20
  • That was a copy paste error. I changed it too, but it still doesn't work.. Guess I'll have to debug some more – Tobilence Mar 05 '21 at 09:49
  • It seems like it has nothing to do with the picker after all - I just updated it with a button instead and still no animation – Tobilence Mar 05 '21 at 09:57
-1

Okay so to get the animation I wanted, I ended up creating an extra component:

struct ImageSwitcher: View {
    
    let firstImage: String
    let secondImage: String
    
    @Binding var firstImageShowing: Bool
    
    var body: some View {
        ZStack {
            Image(firstImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .edgesIgnoringSafeArea(.all)
                .opacity(firstImageShowing ? 1 : 0)
                .transition(.move(edge: .trailing))
                .animation(.easeInOut(duration: 0.17))
                .isHidden(!firstImageShowing)
               
            Image(secondImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .edgesIgnoringSafeArea(.all)
                .opacity(firstImageShowing ? 0 : 1)
                .transition(.move(edge: .leading))
                .isHidden(firstImageShowing)
                .animation(.easeInOut(duration: 0.17))
        }
    }
}

the .isHidden modifier is an extension to hide the image based on a state variable. You can see more details in this post: Dynamically hiding view in SwiftUI

You can obviously modify the transition, but I ended up going with both move and a fade in/out using the transition and opacity modifier.

USAGE:

Create a state variable:

@State private var showFirst = true

Then give it to the component along with your image names:

ImageSwitcher(firstImage: "img-1", secondImage: "img-2", firstImageShowing: $showFirst)
Tobilence
  • 115
  • 1
  • 6