admin管理员组

文章数量:1355703

AsyncImage(url: URL(string: profilImgUrl)) { phase in
    switch phase {
    case .empty:
        ProgressView()
            .scaleEffect(x: 1.5, y: 1.5, anchor: .center)
            .aspectRatio(contentMode: .fit)
            .frame(maxWidth: 36)
    case .success(let image):
        image.resizable()
             .aspectRatio(contentMode: .fit)
             .frame(maxWidth: 36)
             .cornerRadius(50)
    case .failure:
        Image(systemName: "person.circle.fill")
            .resizable()
            .aspectRatio(contentMode: .fit).frame(height: 36)
            .cornerRadius(50)
            .foregroundColor(Color.gray)
    @unknown default:
        Image(systemName: "person.circle.fill")
            .resizable()
            .aspectRatio(contentMode: .fit).frame(height: 36)
            .cornerRadius(50)
            .foregroundColor(Color.gray)
    }

The case .failure gets triggered pretty often, even though the profilImgUrl is 100% valid.

In the Android version, I've set how often it needs to be tried again when the image fails to load and in the end, the images are shown 100% of the time, is it possible to do it in SwiftUI too? If not, what workaround could I do?

AsyncImage(url: URL(string: profilImgUrl)) { phase in
    switch phase {
    case .empty:
        ProgressView()
            .scaleEffect(x: 1.5, y: 1.5, anchor: .center)
            .aspectRatio(contentMode: .fit)
            .frame(maxWidth: 36)
    case .success(let image):
        image.resizable()
             .aspectRatio(contentMode: .fit)
             .frame(maxWidth: 36)
             .cornerRadius(50)
    case .failure:
        Image(systemName: "person.circle.fill")
            .resizable()
            .aspectRatio(contentMode: .fit).frame(height: 36)
            .cornerRadius(50)
            .foregroundColor(Color.gray)
    @unknown default:
        Image(systemName: "person.circle.fill")
            .resizable()
            .aspectRatio(contentMode: .fit).frame(height: 36)
            .cornerRadius(50)
            .foregroundColor(Color.gray)
    }

The case .failure gets triggered pretty often, even though the profilImgUrl is 100% valid.

In the Android version, I've set how often it needs to be tried again when the image fails to load and in the end, the images are shown 100% of the time, is it possible to do it in SwiftUI too? If not, what workaround could I do?

Share Improve this question asked Mar 29 at 19:19 TheGreatCornholioTheGreatCornholio 1,5451 gold badge23 silver badges49 bronze badges 2
  • You could try setting an .id on the AsyncImage with a value that can be changed/incremented on failure. You would obviously only want to do this a limited number of times, perhaps with a very short wait between retries. – Benzy Neez Commented Mar 29 at 20:23
  • Did you try to figure out why the failure happens? Is the request cancelled during scrolling? – CouchDeveloper Commented Mar 31 at 8:02
Add a comment  | 

1 Answer 1

Reset to default 1

You could try this approach using a retryCount and func retryImage(), as shown in this example code:

 
struct ContentView: View {
    var body: some View {
        AutoRetryAsyncImageView(originalUrl: URL(string: "https://example/image.jpg"))
    }
}

struct AutoRetryAsyncImageView: View {
    let originalUrl: URL?
    let maxRetries: Int = 3
    
    @State private var retryCount = 0
    @State private var imageUrl: URL?

    var body: some View {
        VStack {
            AsyncImage(url: imageUrl) { phase in
                switch phase {
                case .empty:
                    ProgressView()
                    
                case .success(let image):
                    image.resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(maxWidth: 36)
                        .cornerRadius(50)
                    
                case .failure:
                    if retryCount < maxRetries {
                        ProgressView("Retrying... \(retryCount)/\(maxRetries)")
                            .onAppear { retryImage() }
                    } else {
                            Image(systemName: "person.circle.fill")
                                .resizable()
                                .aspectRatio(contentMode: .fit).frame(height: 36)
                                .cornerRadius(50)
                                .foregroundColor(Color.gray)
                   }
                    
                @unknown default:
                    Image(systemName: "person.circle.fill")
                           .resizable()
                           .aspectRatio(contentMode: .fit).frame(height: 36)
                           .cornerRadius(50)
                           .foregroundColor(Color.gray)
                }
            }
        }
        .onAppear {
            imageUrl = originalUrl
        }
    }
    
    private func retryImage() {
        guard retryCount < maxRetries else { return }

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            retryCount += 1
            imageUrl = nil  // clear url
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                imageUrl = originalUrl // force reload
            }
        }
    }

}

EDIT-1:

you can also try this alternative approach having the parent view control the retries.


struct ContentView: View {
    @State private var retryCount = 0
    let maxRetries: Int = 3

    var body: some View {
        AutoRetryAsyncImageView(maxRetries: maxRetries, retryCount: $retryCount)
        .onChange(of: retryCount) {
            print("---> retryCount: \(retryCount)")
            guard retryCount < maxRetries else { return }
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                retryCount += 1
            }
        }
    }
}

struct AutoRetryAsyncImageView: View {
    let maxRetries: Int
    @Binding var retryCount: Int
    
    let imageUrl: URL? = URL(string: "https://example/image.jpg")
    
    var body: some View {
        VStack {
            AsyncImage(url: imageUrl) { phase in
                switch phase {
                case .empty:
                    ProgressView()
                    
                case .success(let image):
                    image.resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(maxWidth: 36)
                        .cornerRadius(50)
                    
                case .failure:
                    if retryCount < maxRetries {
                        ProgressView("Retrying... \(retryCount)/\(maxRetries)")
                            .onAppear {
                                retryCount += 1
                            }
                    } else {
                        Image(systemName: "person.circle.fill")
                            .resizable()
                            .aspectRatio(contentMode: .fit).frame(height: 36)
                            .cornerRadius(50)
                            .foregroundColor(Color.gray)
                    }
                    
                @unknown default:
                    Image(systemName: "person.circle.fill")
                        .resizable()
                        .aspectRatio(contentMode: .fit).frame(height: 36)
                        .cornerRadius(50)
                        .foregroundColor(Color.gray)
                }
            }
        }
    }
    
}



本文标签: iosSwiftUI AsyncImagetry again on image loading failureStack Overflow