I created an image view like the ChildView
below.
But when I update the image, it has some strange behavior.
The value is still the old one after the closure of the old onChange
closure being called in this case. The url
always has an one round delay.
But when I use the onChange in the content view, the url
is up to date again in the block.
How does this onChange
work?
import SwiftUI
struct ContentView: View {
@State var url:URL? = Bundle.main.url(forResource: "image1", withExtension: "jpeg")
let alt = Bundle.main.url(forResource: "image1", withExtension: "jpeg")
let alt1 = Bundle.main.url(forResource: "image2", withExtension: "PNG")
var body: some View {
VStack {
Button("Change Image") {
url == alt1 ? (url = alt) : (url = alt1)
}
Text("url : \(url?.lastPathComponent ?? "nil")")
ChildView(url: url)
}
.padding()
}
}
struct ChildView: View {
var url:URL?
@State var image: UIImage? = nil
var body: some View {
VStack{
if let image = image {
Image(uiImage: image)
.resizable()
.scaledToFit()
}
}
/*
.task(id:url) {
loadImage() //works fine.
}
*/
.onAppear{
loadImage()
}
.onChange(of: url) { value in
print("old OnChange \(url?.lastPathComponent) \(value?.lastPathComponent)")
// "url: old, value: new" <--------- very strange here.
loadImage()
}
.onChange(of:url) { old, new in //<--------- works fine, but only available in iOS 17.
}
}
func loadImage() {
if let url = url {
image = UIImage(contentsOfFile: url.path)
}
}
}
I created an image view like the ChildView
below.
But when I update the image, it has some strange behavior.
The value is still the old one after the closure of the old onChange
closure being called in this case. The url
always has an one round delay.
But when I use the onChange in the content view, the url
is up to date again in the block.
How does this onChange
work?
import SwiftUI
struct ContentView: View {
@State var url:URL? = Bundle.main.url(forResource: "image1", withExtension: "jpeg")
let alt = Bundle.main.url(forResource: "image1", withExtension: "jpeg")
let alt1 = Bundle.main.url(forResource: "image2", withExtension: "PNG")
var body: some View {
VStack {
Button("Change Image") {
url == alt1 ? (url = alt) : (url = alt1)
}
Text("url : \(url?.lastPathComponent ?? "nil")")
ChildView(url: url)
}
.padding()
}
}
struct ChildView: View {
var url:URL?
@State var image: UIImage? = nil
var body: some View {
VStack{
if let image = image {
Image(uiImage: image)
.resizable()
.scaledToFit()
}
}
/*
.task(id:url) {
loadImage() //works fine.
}
*/
.onAppear{
loadImage()
}
.onChange(of: url) { value in
print("old OnChange \(url?.lastPathComponent) \(value?.lastPathComponent)")
// "url: old, value: new" <--------- very strange here.
loadImage()
}
.onChange(of:url) { old, new in //<--------- works fine, but only available in iOS 17.
}
}
func loadImage() {
if let url = url {
image = UIImage(contentsOfFile: url.path)
}
}
}
Share
edited Nov 28, 2024 at 11:05
Perry Wang
asked Nov 28, 2024 at 10:48
Perry WangPerry Wang
1992 silver badges10 bronze badges
3 Answers
Reset to default 1You are using the old (now deprecated) version of .onChange
, because you need to support iOS versions before iOS 17.
The documentation to the old version includes a note:
This modifier’s closure captures values that represent the state before the change. The new modifiers capture values that correspond to the new state. The new behavior makes it easier to perform updates that rely on values other than the one that caused the modifier’s closure to run.
So when your closure is run, the value of url
is still the old one. You are calling loadImage
from inside the closure, which therefore loads the old image.
To fix, try adding a parameter to loadImage
:
func loadImage(url: URL?) {
// ... implementation as before
}
Now supply the URL to load when calling the function. In the case of the call in the .onChange
closure, this means passing value
.onAppear{
loadImage(url: url) //