Xcode 15 Beta - Preview is showing remotely loaded Image, however, the development device is not.

Hello, I am relatively new to iOS development and am trying to learn by creating an app to connect to my SMB server to view and manipulate files. I am aware that the built in Files app has SMB capabilities, but I wanted to take a crack at building my own as I saw a few shortcomings and places where it could be improved.

Anyway, I am running into a weird issue. I am working on the functionality that would show the user any particular image that they choose in a fullscreen view. The code seems to be streaming in the image and displaying it just fine, and the Xcode Preview would seem to be confirming that. However, my device will not, under any circumstances display the Image! I have tried with multiple images, and also building the app and running it independently of Xcode, which points to an issue with my code. But then why is the Xcode Preview displaying the Image just fine? I also tried adding a Text view just to make sure the view as whole was updating properly and it was, the Text view displayed just fine. I understand that I'm on beta software, so if this is a bug with Xcode itself, or iOS, then no complaints here! I'll just try and find a way to work around it. I'm really only posting this to make sure it's not user error! I'll also attach my code of course, as well as a couple of screenshots just so you know I'm not crazy. Thanks!

I tried to take a screenshot of the whole editor for you because usually in these cases, the more information, the better, but for some reason, when I tried to uploaded, it just gave me a generic error response and said try again later.

//
//  ImageView.swift
//  Akasha
//
//  Created by Dylan on 6/20/23.
//

import SwiftUI
import AMSMB2
import Combine

struct ImageView: View {
    @ObservedObject var imageLoader: ImageLoader
    @State var image: UIImage = UIImage()
    
    init(withUrl url: String) {
        imageLoader = ImageLoader(urlString: url)
    }
    
    var body: some View {
        VStack {
            Image(uiImage: image)
                .aspectRatio(contentMode: .fill)
                .onReceive(imageLoader.didChange) { data in
                    self.image = UIImage(data: data) ?? UIImage()
            }
            Spacer()
            Text("Hello World!")
        }
    }
}

class ImageLoader: ObservableObject {
    var didChange = PassthroughSubject<Data, Never>()
    var data = Data() {
        didSet {
            didChange.send(data)
        }
    }
    
    let client = AMSMB2(url: URL(string: "smb://10.0.0.2/")!, credential: URLCredential(user: "user", password: "password", persistence: .forSession))

    init(urlString:String) {
        guard let url = URL(string: fetchImage(path: urlString).absoluteString) else { return }
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let data = data, self != nil else { return }
            DispatchQueue.main.async { [weak self] in
                self?.data = data
            }
        }
        task.resume()
    }
    
    func fetchImage(path: String) -> URL {
        do {
            var cacheURLDirectory: URL
            cacheURLDirectory = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
            let fullCacheURLPath = cacheURLDirectory.absoluteString + path
            let fullCacheURL = URL(string: fullCacheURLPath)!
            client?.connectShare(name: "media") { error in
                if error != nil {
                    print("connectShare Error: ")
                    print(error!)
                }
                self.client?.downloadItem(atPath: path, to: fullCacheURL, progress: AMSMB2.ReadProgressHandler(downloadProgressHandler(bytes:total:)), completionHandler: AMSMB2.SimpleCompletionHandler(downloadCompletionHandler(error:)))
            }
            return fullCacheURL
        } catch {
            print ("Error Setting Cache Directory for Image")
            return URL(string: "")!
        }
        
        func downloadProgressHandler(bytes: Int64, total: Int64) -> Bool {
            return true
        }
        
        func downloadCompletionHandler(error: Error?) -> Void {
        }
    }
}

#Preview {
    ImageView(withUrl: "Old Hard Drive.webp")
}

And if this turns out of course to be a bug with Xcode itself and NOT user error, I will file it in the Feedback app (if there is a spot in there for Xcode... if not, I will find out where I need to go). Thanks everyone!

Answered by StellarAelwyd in 756714022

In case anyone else is having a similar issue as I am, I have managed to solve it. I still do not know the cause, so I went ahead and filed a bug report, but I do know the fix. Do not fret, I will not leave you in the dark for when you inevitably stumble upon this in ten years time just to see "Solved it!".

As much as I would like to take credit for it, credit should actually go to @timbretimbre on StackOverflow for his superb recommendation to streamline my code. Here's the link to the original question just for thoroughness' sake: https://stackoverflow.com/questions/76524832/image-view-is-rendering-in-xcode-preview-but-not-on-device

First, I completely deleted the "@State var image" variable at the top. Next, in the ImageLoader class, replace the data variable with a normal, @Published variable and initialize it to a Data() object. Basically, just delete the didSet code block and add @Published. And then finally, remove the .onReceive bit from the Image view up top and set the uiImage attribute of the Image View to "UIImage(data: imageLoader.data)!"

Here's a summary:

Remove the "image" variable:

struct ImageView: View {
    @ObservedObject var imageLoader: ImageLoader
    
    init(withUrl url: String) {
        imageLoader = ImageLoader(urlString: url)
    }

Refactor the data variable inside the ImageLoader class:

class ImageLoader: ObservableObject {
    var didChange = PassthroughSubject<Data, Never>()
    @Published var data = Data()

Refactor the Image View:

Image(uiImage: UIImage(data: imageLoader.data)!)
                .resizable()
                .aspectRatio(contentMode: .fit)

Hope this helps someone!

Could this be a result of App Transport Security which is active on iOS devices but not in the simulator?

Accepted Answer

In case anyone else is having a similar issue as I am, I have managed to solve it. I still do not know the cause, so I went ahead and filed a bug report, but I do know the fix. Do not fret, I will not leave you in the dark for when you inevitably stumble upon this in ten years time just to see "Solved it!".

As much as I would like to take credit for it, credit should actually go to @timbretimbre on StackOverflow for his superb recommendation to streamline my code. Here's the link to the original question just for thoroughness' sake: https://stackoverflow.com/questions/76524832/image-view-is-rendering-in-xcode-preview-but-not-on-device

First, I completely deleted the "@State var image" variable at the top. Next, in the ImageLoader class, replace the data variable with a normal, @Published variable and initialize it to a Data() object. Basically, just delete the didSet code block and add @Published. And then finally, remove the .onReceive bit from the Image view up top and set the uiImage attribute of the Image View to "UIImage(data: imageLoader.data)!"

Here's a summary:

Remove the "image" variable:

struct ImageView: View {
    @ObservedObject var imageLoader: ImageLoader
    
    init(withUrl url: String) {
        imageLoader = ImageLoader(urlString: url)
    }

Refactor the data variable inside the ImageLoader class:

class ImageLoader: ObservableObject {
    var didChange = PassthroughSubject<Data, Never>()
    @Published var data = Data()

Refactor the Image View:

Image(uiImage: UIImage(data: imageLoader.data)!)
                .resizable()
                .aspectRatio(contentMode: .fit)

Hope this helps someone!

Xcode 15 Beta - Preview is showing remotely loaded Image, however, the development device is not.
 
 
Q