Asynchronous Image Loading Bug - swift

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.

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.

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)

Related

Thread 14 SwiftUI - Help to call an API and log in

Hello fellow Swift Coders. I'm a beginner, and i'm trying to call a web API then log in to have access to a new view.
This is my code, everything seems running well (before i try to put the login + pass)
import SwiftUI
import Foundation
struct ContentView: View {
#State private var username = ""
#State private var password = ""
#State private var loginSuccessful = false
var body: some View {
NavigationView {
ZStack {
Color("primaryColor").ignoresSafeArea()
VStack {
HStack(alignment: .top) {
Text("FOLD")
.font(.system(size: 50))
.foregroundColor(Color(.white))
.padding(.bottom, 80)
}
ZStack{
Rectangle()
.frame(width: 345, height: 400)
.cornerRadius(25)
.foregroundColor(Color("secondaryColor"))
.shadow(radius: 10, x: 10, y: 3)
VStack(alignment: .leading) {
Text("Se connecter")
.font(.system(size: 20))
.foregroundColor(.white)
.bold()
.padding(.bottom,50)
VStack(alignment: .leading) {
Text("Email")
.font(.callout)
.bold()
.foregroundColor(.white)
HStack {
TextField("Enter email", text: $username)
.padding()
.font(.system(size: 14))
.foregroundColor(Color("primaryColor"))
.frame(width: 270, height: 50)
.background(.white)
.cornerRadius(10)
}
}
.frame(width: 270, height: 60)
.padding(.bottom, 30)
VStack(alignment: .leading) {
Text("Password")
.font(.callout)
.bold()
.foregroundColor(.white)
HStack {
SecureField("Password", text: $password)
.padding()
.font(.system(size: 14))
.foregroundColor(Color("primaryColor"))
.frame(width: 270, height: 50)
.background(.white)
.cornerRadius(10)
}
}
.frame(width: 270, height: 60)
.padding(.bottom, 50)
Button(action: {
logIn(username: username, password: password)
}) {
Text("Log in")
}
.font(.headline)
.foregroundColor(.white)
.frame(width: 270,height: 40)
.background(Color("buttonColor"))
.cornerRadius(10)
}
.sheet(isPresented: $loginSuccessful) {
QRscan()
}
}
}
Spacer()
}
}
}
func logIn(username: String, password: String) {
let url = URL(string: "https://foldlab.io:8081/loginScanAccount")!
let body = ["username": username, "password": password]
let jsonData = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print(error)
return
}
if let data = data {
let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
// Check if the login was successful
if json["status"] as! String == "success" {
DispatchQueue.main.async {
self.loginSuccessful = true
}
} else {
print("Error: Invalid username or password.")
}
}
}
task.resume()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
The thing is, when i'm trying on the simulator or the preview, its crashing. And i have an error i've never seen.
What should i do ?
Thread 14: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0., NSJSONSerializationErrorIndex=0}
Thanks a lot.
I tried to check on SOF, but i didnt really understand the answers

Swift - recognize a link and make it clickable

I have an app that loads blog posts from a Wordpress site.
Now loading the text, images and video's work fine.
In the blog posts from Wordpress are links. Like for example: https://eenvandaag.avrotros.nl/item/ondanks-droogte-op-de-veluwe-onttrekt-kartonfabriek-parenco-er-jaarlijks-miljarden-liters-grondwater/
When the app loads the post, the link is displayed in plain text.
Is there a way to recognise the links and make these "links" clickable and guide the user to safari?
I've been searching the internet for hours but couldn't find a fitting solution.
Every post has a different link. So the goal is that the link is recognised and made clickable.
I found some examples, for example:
var body: some View {
VStack {
Text(attributedString)
Text("Check out [this link](https://stack.com)")
}
}
var attributedString: AttributedString {
var attributedText = AttributedString("Visit website")
attributedText.link = URL(string: "https://stack.com")
return attributedText
}
But then you have to set the link.
So that doesn't work for me since the link is different for every post.
Can someone point me in the right direction?
This is the code for my PostContentView
import AVKit
import CachedAsyncImage
import SwiftUI
import WPArticleView
struct PostContentView: View {
let post: Post
var body: some View {
VStack(alignment: .leading, spacing: 32) {
if let url = post.embedded.medias?.first?.url {
CachedAsyncImage(url: url, urlCache: .imageCache) { phase in
switch phase {
case.empty:
ProgressView()
.progressViewStyle(.circular)
.frame(maxWidth: .infinity)
case.success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.cornerRadius(8)
.frame(height: 200)
.padding(.bottom, 16)
.padding(.top, -10)
case.failure:
ZStack{
Image(systemName: "wifi.slash")
.foregroundColor(Color(UIColor.white))
RoundedRectangle(cornerRadius: 6)
.foregroundColor(Color(UIColor.systemGray5))
.frame(height: 200)
.frame(maxWidth: .infinity)
}
#unknown default:
EmptyView()
}
}
}
VStack(alignment: .leading, spacing: 16) {
Text(post.title.text)
.font(.title2).bold()
.padding(.horizontal)
.foregroundColor(.white)
Text(post.date.formattedDateString)
.font(.footnote)
.padding(.horizontal)
.foregroundColor(.white)
WPArticleView(htmlBody: post.content.rendered) { blocks in
ForEach(blocks.indices, id: \.self) { id in
blocks[id]
.padding(.bottom, 6)
.padding(.horizontal)
.foregroundColor(.white)
.textSelection(.enabled)
}
} text: { attributedText in
Text(attributedText)
} image: { imageURL in
CachedAsyncImage(url: imageURL, urlCache: .imageCache) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.cornerRadius(8)
} placeholder: {
//EmptyView()
ProgressView()
.frame(maxWidth: .infinity)
}
} video: { videoURL in
VideoPlayer(player: AVPlayer(url: videoURL))
.frame(minHeight: 250)
//.frame(minHeight: 400)
.cornerRadius(8)
}
}
}
.padding(.horizontal, 20)
}
}
And my Post screen:
import Foundation
struct Post: Decodable, Identifiable {
let id: Int
let date: Date
let title: Title
let content: Content
let embedded: Embedded
enum CodingKeys: String, CodingKey {
case date = "date"
case title = "title"
case content = "content"
case embedded = "_embedded"
case id = "id"
}
}
EDIT:
Here is an example from a post:

Having trouble with showing another view

I'm currently trying to input another listview in my contentView file to test if it'll show, but for some reason it isn't showing the list. I'm having a bit of trouble understanding why this is happening as I am not receiving any error message.
This is the code for the list file
import SwiftUI
extension Image{
func anotherImgModifier() -> some View{
self
.resizable()
.scaledToFill()
.frame( width: 75, height: 75)
.cornerRadius(9)
}
}
struct PokeListView: View {
#State var imgURL: String = ""
#EnvironmentObject var pokeWebService: PokeWebService
//functions
// func loadImage() async -> [Image]{
// for
// }
var body: some View {
NavigationView {
List( pokeWebService.pokeList?.results ?? [], id: \.id){ pokemon in
NavigationLink(destination: PokeDetailsView(urlString: pokemon.url, counter: 4, name: pokemon.name)) {
AsyncImage(url:URL(string: "https://play.pokemonshowdown.com/sprites/bw/\(pokemon.name).png")){ image in
image.anotherImgModifier()
}
placeholder: {
Image(systemName: "photo.circle.fill").iconModifer()
}.padding(40)
Text(pokemon.name.uppercased()).font(.system(size: 15, weight: .heavy, design: .rounded))
.foregroundColor(.gray)
.task{
do{
try await pokeWebService.getPokemonFromPokemonList(from: pokemon.url)
} catch{
print("---> task error: \(error)")
}
}
}
}
}
.task {
do{
try await pokeWebService.getPokemonList()
} catch{
print("---> task error: \(error)")
}
}
}
}
struct PokeListView_Previews: PreviewProvider {
static var previews: some View {
PokeListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(PokeWebService())
}
}
This is the code for the ContentView where I was trying to input the list file.
import SwiftUI
struct ContentView: View {
#StateObject var newsWebService = NewsWebService()
#StateObject var pokeWebService = PokeWebService()
let gbImg = Image("pokeball").resizable()
#State private var gridLayout: [GridItem] = [ GridItem(.flexible()), GridItem(.flexible())]
#State private var gridColumn: Int = 2
#State var selection: Int? = nil
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false, content: {
VStack(alignment: .center, spacing: 15, content: {
Spacer()
NewsCapsule()
//GRID
//BERRIES, POKEMON, GAMES
GroupBox(label: Label{
Text("PokéStuff")
} icon: {
Image("pokeball").resizable().scaledToFit().frame(width: 30, height: 30, alignment: .leading)
}
, content: {
PokeListView()
}).padding(.horizontal, 20).foregroundColor(.red)
})//:VSTACK
})//:SCROLLVIEW
.navigationBarTitle("Pokemon",displayMode: .large)
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Image(systemName: "moon.circle")
.resizable()
.scaledToFit()
.font(.title2)
.foregroundColor(.red)
})
})
}//:NAVIGATIONBAR
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NewsWebService())
.environmentObject(PokeWebService())
}
}
How would I get to fix this?
EDIT-1:
with further tests, this is what worked for me:
in ContentView, add .frame(height: 666) to the VStack {...}.
This is the reason why you do not see anything. You need a frame height.
Also in ContentView, add .environmentObject(pokeWebService) to the NavigationView,
and just use PokeListView(). This is to pass the pokeWebService
to that view. After that, all works for me. You may want to experiment
with different frame sizes and such likes. You should also remove the NavigationView from your PokeListView, there is no need for it.

Why am I not able to connect my CoreData in SwiftUI?

Disclaimer: I am trying to learn the basics of IOS development, so this question might be very basic.
I'm currently building out my first IOS project, which consists of pulling a random Poem from an API and then giving the user the possibility to save that poem to a "Saved Poem" list. My app currently has a Poem Detail screen (ContentView) and a Home Page screen (where the saved poem list will be).
I've attempted to follow Paul Hudson's tutorial on CoreData (https://www.youtube.com/watch?v=7_Afen3PlDE&ab_channel=PaulHudson). Currently, my goal is to save a poem once the "Bookmark" button on the Detail Screen is tapped. Once a poem saved to CoreData, I would like to display it in a list on the home page.
Code for the Detail View (which includes the Bookmark button)
import SwiftUI
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
#Environment(\.managedObjectContext) var moc
var currentDate = Text(Date().addingTimeInterval(600), style: .date)
var body: some View {
VStack {
HStack{
Button(action: {}) {
Image(systemName: "arrow.backward")
.font(.system(size: 25, weight: .heavy))
.foregroundColor(.black)
}
Spacer(minLength: 0)
Button(action: {
let savedpoem = SavedPoem(context: self.moc)
savedpoem.id = UUID()
savedpoem.title = "\(poem.title)"
savedpoem.author = "\(poem.author)"
savedpoem.lines = "\(joined)"
try? self.moc.save()
}) {
Image(systemName: "bookmark")
.font(.system(size: 25, weight: .heavy))
.foregroundColor(.black)
}
}
.padding(.vertical, 10)
ScrollView {
VStack {
HStack{
VStack (alignment: .leading) {
Text("Today's Poem, \(currentDate)")
.font(.subheadline)
.foregroundColor(Color.gray)
.padding(.bottom, 20)
.padding(.top, 10)
if let poem = fetch.poems.first {
let joined = poem.lines.joined(separator: "\n")
Text("\(poem.title)")
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.black)
.padding(.bottom, 20)
.lineSpacing(0)
Text("BY "+poem.author.uppercased())
.font(.subheadline)
.foregroundColor(Color.gray)
.padding(.bottom, 20)
HStack {
Text("\(joined)")
.font(.body)
.foregroundColor(.black)
.padding(.bottom)
.lineSpacing(5)
Spacer()
}
} else {
Spacer()
}
}
}
}
}
Button("Get Next Poem") { fetch.getPoem() }
}
.background(Color.white.ignoresSafeArea())
.padding(.horizontal)
}
Code for the Home Page View
import SwiftUI
import CoreData
struct HomeView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: SavedPoem.entity(), sortDescriptors: []) var savedpoems:FetchedResults<SavedPoem>
var body: some View {
VStack{
List{
ForEach(savedpoems, id: \.id) { savedpoem in
Text(savedpoem.name ?? "Unkown")
}
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
My CoreData Data Model
And finally, my Data Model to pull from the API:
import Foundation
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
// 1.
#Published var poems = [Poem]()
init() {
getPoem()
}
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
Unfortunately, this code doesn't build and is throwing me the following errors:
On the Home Page:
Cannot find type 'SavedPoem' in scope
Cannot infer key path type from context; consider explicitly specifying a root type
Generic parameter 'Content' could not be inferred
Missing argument for parameter 'content' in call
In Detail View:
Cannot Find "Poem" in Scope
Any ideas? Thanks in advance.

How Should My Code Run When using SwiftUI And Websockets

I am making a client that connect to a websocket and shows different pages. I am looking for help on how I should order my code. First the user logs in and it connects them to the websocket to verify and if they are authorized it will continue, if not it will show an error.
TLDR
My code need to somehow connect to a websocket, go to a new page, and show info. My code also needs to alert the user about any errors.
var request = URLRequest(url: URL(string: "wss://link")!)
struct LoginView: View {
#State private var loggedIn = false
#State private var loginCode = ""
var body: some View {
NavigationView {
VStack(spacing: 30) {
NavigationLink(destination: newView().navigationBarHidden(true), isActive: $loggedIn) { EmptyView() }
HStack {
Image(systemName: "person").foregroundColor(.gray)
TextField("Login Code", text: $loginCode)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.frame(width: 300)
.offset(y: -75)
})
.buttonStyle(ContainedButtonStyle())
.offset(y: -50)
}
.navigationBarTitle("Log In")
}
}
}
func connectToSocket(loginCode: String) {
request.addValue("Bearer \(loginCode)", forHTTPHeaderField: "Authorization")
let ws = WebSocket(request: request)
struct newView: View {
#State private var accountsCheckedShown = false
let username = "MarcoD1337"
let games = 5
let lives = 10
let money = "$5/$5"
let status = "Ready For Cashout"
var body: some View {
NavigationView {
TabView {
ZStack {
List{
HStack {
ZStack {
Circle().fill(Color(red: 0.913, green: 0.737, blue: 0.447, opacity: 1))
.frame(maxWidth: 50, maxHeight: 50)
Text("🇮🇱")
.font(.largeTitle)
.multilineTextAlignment(.center)
}
VStack(alignment: .leading) {
Text(username)
.font(.title)
.fontWeight(.bold)
.minimumScaleFactor(0.25)
Text("Games: \(games)")
.minimumScaleFactor(0.5)
Text("Lives: \(lives)")
.minimumScaleFactor(0.5)
Text("\(money) Available")
.minimumScaleFactor(0.5)
Text("Status: \(status)")
.minimumScaleFactor(0.5)
}
.padding()
}
}
.navigationTitle("Accounts")
}
.tabItem {
Image(systemName: "star")
Text("User Info")
}
}
}
}
}
ws.event.open = { }
ws.event.message = { message in
let msg = JSON(message)
if msg["type"] == "" { }
}
ws.event.error = { error in
//errorLoginView(error: "\(error)")
}
}
I've used WebSocket(StarScream) in a Swift demo app.
I'd recommend using a standalone state object (#ObservableObject or #EnvironmentObject)to handle websocket. Think of it as a shared websocket service.
In this service, you publish a couple of events based on your needs.
E.g.;
enum LoginState {
case initial, authOK, authFail
}
final class Websocket: ObservableObject {
#Published var state: LoginState = .initial
// implement websocket callbacks
// e.g.; change state to .authFail in error handler
}
Your LoginView simply observes state changes:
struct LoginView: View {
#ObservedObject var ws = Websocket()
var body: some View {
// setup websocket as needed
switch ws.state { // supported in Xcode 12 as I recall, use if otherwise
case .authOK:
// some view
// other cases
}
}
}
This is the overall picture, you need to fill in the implementation yourself.
I think this is sufficient to get you started on the right track.