swiftswiftuiswiftsoup

Swift how can I make this function async to get rid of my application warning


I am using SwiftUI 4.0 and have the SwiftSoup package . When I try to load a website I now get this message (happens for any website)

Synchronous URL loading of https://www.cnn.com should not occur on this application's main thread as it may lead to UI unresponsiveness. Please switch to an asynchronous networking API such as URLSession.

It specifically occurs during this section of the code

if let html = try? String(contentsOf: myURL, encoding: .utf8) {

does anyone have a suggestion on how to fix this issue . This is the function I am using

import Foundation
import SwiftUI
import Combine
import SwiftSoup

func NewLinkRequest(_ LinkUrl: String) ->(LinkUrl: String ,LinkTitle: String ,LinkImage: String)
{
    var newTitle = ""
    
    
    let urlm = URL(string: LinkUrl)
    
    guard let myURL = urlm else {
        return ("","Failed to get url", "")
    }
    
    if let html = try? String(contentsOf: myURL, encoding: .utf8) {
        
        do {
            let doc: Document = try SwiftSoup.parseBodyFragment(html)
            let headerTitle = try doc.title()
            
            let firstImage = try doc.select("img").attr("src")
            
            newTitle = headerTitle
            
            
            return (LinkUrl,newTitle, firstImage)
            
            
        } catch Exception.Error( _, let message) {
            print("Message: \(message)")
        } catch {
            print("error")
        }
        return ("","", "")
    } else {
        
        return ("","Failed to get url", "")
    }
    
}

Solution

  • The Problem

    This error happens because String(contentsOf: ) is synchronous and can cause your UI to hang. Instead, use a URLSession like below. The following function, given a URL, will asynchronously get you the String.

    The Solution

    func fetchFromURL(_ url: URL) async -> String{
       let session = URLSession.shared
       let (theStringAsData, _) = try await session.data(from: articleUrl)
       if let returnableString = String(data: theStringAsData, encoding: .utf8)  
       {
           return returnableAsString
       } else {
           return ""
       }
    }
    

    Code breakdown:

    1. let session is the shared URLSession for your application - docs here: URLSession.Shared
    2. let (theStringAsData, _) returns a Data object and a URLResponse - docs for this method here: https://developer.apple.com/documentation/foundation/urlsession/3767352-data
    3. We check to ensure this data is not nil, and if the typecast to String works, we return the new String. Otherwise, we return an empty.

    Example Usage:

    import SwiftSoup
    
       Task{
           let theString = fetchFromURL(URLHERE) //Put your URL here!
           //We use a do/catch block here because SwiftSoup.parse can throw
         do{
            let document = try SwiftSoup.parse(theString) //This is now the parsed document if it worked
            print(document)
         } catch { //SwiftSoup failed to parse the String into a document, so we need to handle the error
             print("Failed to parse")
       }
    }