简体   繁体   中英

Animating Background Image based on Picker value SwiftUI

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

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

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM