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.