Reputation: 58
I am hitting a strange bug when it comes to Asynchronous Image Loading, where when I enter a view, the shows up like it is suppose to do, then for some reason the image drops and all I see is the "Loading..." placeholder. I used this tutorial when building my loader and the following script is my Article View. I have a Global Functions file, which includes reference to Combine and Foundation for my various functions through out the app. I am just not fully understanding why the image is showing for a brief moment, then calls the placeholder. Thanks!
import SwiftUI
struct ArticleView: View {
@Environment(\.imageCache) var cache: ImageCache
var articleID: Int
@ObservedObject private var data = Result2()
let defaults = UserDefaults.standard
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
init(articleID: Int){
self.articleID = articleID
self.data.results.append(Story.init(id: 0, title: "test", image: "", story: "", published: "", author: ""))
self.loadArticle(CDNLink: "http://\(self.defaults.object(forKey: "domain") as! String)/cdn?funct=fetchArticle&articleID=\(self.articleID)")
}
var backBtn: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label:{
Image(systemName: "lessthan.circle")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(Color.primaryRed)
.padding(.top, 6)
.padding(.leading, 10)
})
}
var body: some View {
VStack(alignment: .leading){
ScrollView {
ForEach(data.results, id: \.id) { result in
Group{
AsyncImage(
url: URL(string: result.image)!,
placeholder: Text("Loading..."), configuration: { $0.resizable() })
.frame(height: 250)
.aspectRatio(contentMode: .fill)
VStack(alignment: .leading) {
Text("\(result.title)")
.font(.system(size: 18))
.fontWeight(.heavy)
.foregroundColor(.primary)
.multilineTextAlignment(.leading)
HStack {
Text("\(result.author)")
.font(.system(size: 12))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
Text("|")
.font(.system(size: 12))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
Text("\(result.published)")
.font(.system(size: 12))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
}.frame(width: UIScreen.main.bounds.size.width - 40, alignment: .leading)
}.frame(width: UIScreen.main.bounds.size.width - 40)
}
Text("\(result.story)")
}
}
}
.frame(maxWidth: .infinity)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backBtn)
}
func loadArticle(CDNLink: String) {
guard let url = URL(string: CDNLink) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
return
} else {
do {
let decodedResponse = try JSONDecoder().decode(Response2.self, from: data!)
print(decodedResponse)
DispatchQueue.main.async {
self.data.results.append(contentsOf: decodedResponse.results)
self.data.results.remove(at: 0)
}
} catch let err {
print("Error parsing: \(err)")
}
}
}.resume()
}
}
struct Response2: Codable {
var results: [Story]
}
struct Story: Codable, Identifiable {
var id: Int
var title: String
var image: String
var story: String
var published: String
var author: String
}
struct ArticleView_Previews: PreviewProvider {
static var previews: some View {
ArticleView(articleID: 0)
}
}
Edit: I have made the changes suggested and I am still seeing the error, you can see the error live by checking out this link.
Upvotes: 0
Views: 1192
Reputation: 58
Okay so I created a new solution to patch this issue up and I am very glad that this little work around is working. So, I started looking at what I could do server side to optimize content being pulled into the app. What I did was use PHP to encode the image with base64, then the app pulls it in and decodes the base64 data and the image populates with lightning speed!
Server Side code:
$article['image'] = base64_encode(file_get_contents(PATH . "/cache/content/topstory/" . $row['app_article']));
Client Side code:
let dataDecoded:NSData = NSData(base64Encoded: result.image, options: NSData.Base64DecodingOptions(rawValue: 0))!
let decodedimage:UIImage = UIImage(data: dataDecoded as Data)!
Image(uiImage: decodedimage)
.frame(height: 250)
.aspectRatio(contentMode: .fill)
Upvotes: 0
Reputation: 444
Hi most likely it is due to the fact that you are using @ObservedObject
. This object is discarded and initialized every time your view state changes and is triggering a view re-render. Try using @StateObject
instead of @ObservedObject
. The @StateObject
will be initialized only once in the view.
A @StateObject
can be also used with an ObservedObject
model. It is kind of a combination of @ObservedObject
and @State
.
Upvotes: 1