skip to Main Content

I am working on an iOS application in Swift that fetches song lyrics using this API https://lyricsovh.docs.apiary.io/#reference/0/lyrics-of-a-song/search?console=1. However, I am facing an issue with constructing the correct URL when the artist or title names contain multiple words.

Problem:
When the artist name contains spaces (e.g., "Burna Boy"), the URL does not format correctly, leading to a malformed request. The desired format is like https://api.example.com/v1/Burna%20Boy/City%20Boys, but my function generates https://api.example.com/v1/Burna%20Boy%20City%20Boys.
Code:
Here is the Swift function I use to fetch the lyrics:

    func fetchLyrics(artist: String, title: String) async throws -> String {
        let fullPath = "(artist)/(title)"
        
        // Encode the entire path
        let encodedPath = fullPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
        
        // Construct the endpoint URL
        let endpoint = "(baseURL)/(encodedPath)"
        print("---->", endpoint)
        
        guard let url = URL(string: endpoint) else {
            throw NetworkError.invalidURL
        }

        var request = URLRequest(url: url)
        request.httpMethod = "GET"

        do {
            let (data, response) = try await session.data(for: request)
            
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                throw NetworkError.invalidResponse
            }

            do {
                let lyricsResponse = try JSONDecoder().decode(LyricsResponse.self, from: data)
                return lyricsResponse.lyrics
            } catch {
                throw NetworkError.decodingFailed
            }
        } catch {
            throw NetworkError.requestFailed
        }
    }

}
Expected URL Example:
https://api.example.com/v1/Burna%20Boy/City%20Boys

Actual Output:
https://api.example.com/v1/Burna%20Boy%20City%20Boys

Question:
How can I modify my function to correctly encode the URL path while preserving the necessary structure for the API (i.e., keeping the slash between the artist and the song title and encoding spaces as %20)?

Additional Information:
I am using Swift 5 and URLSession for network requests. The baseURL in my function is correct and ends with /v1.

Attempts:
I tried adjusting the addingPercentEncoding parameters and concatenation sequence, but the issue persists with multi-word artists and titles.

Any suggestions or corrections to my approach would be greatly appreciated!

2

Answers


  1. There is no problem with the formating of the URL.
    When the artist name contains spaces (e.g., "Burna Boy"), the URL format is correctly
    created.

    Here is my test code (SwiftUI) that works for me:

    struct ContentView: View {
        @State private var lyrics = ""
        let baseURL = "https://api.lyrics.ovh/v1"
    
        var body: some View {
            Text(lyrics)
            .task {
                do {
                    lyrics = try await fetchLyrics(artist: "Burna Boy", title: "City Boys")
                    print("----> lyrics: (lyrics)")
                } catch {
                    print("----> error: (error)")
                }
            }
        }
    
        func fetchLyrics(artist: String, title: String) async throws -> String {
            let fullPath = "(artist)/(title)"
            
            // Encode the entire path
            let encodedPath = fullPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
       
            // Construct the endpoint URL
            let endpoint = "(baseURL)/(encodedPath)"
            print("----> endpoint: (endpoint)")
            
            guard let url = URL(string: endpoint) else {
                throw NetworkError.invalidURL
            }
            
            var request = URLRequest(url: url)
            request.httpMethod = "GET"
            
            do {
                let (data, response) = try await URLSession.shared.data(for: request)
        //        print("----> response: (String(data: data, encoding: .utf8) as AnyObject)")
                
                guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                    throw NetworkError.invalidResponse
                }
                do {
                    let lyricsResponse = try JSONDecoder().decode(LyricsResponse.self, from: data)
                    return lyricsResponse.lyrics
                } catch {
                    throw NetworkError.decodingFailed
                }
            } catch {
                throw NetworkError.requestFailed
            }
        }
       
    }
    
    struct LyricsResponse: Codable {
        let lyrics: String
    }
    
    enum NetworkError: String, Error {
        case invalidURL = "invalidURL"
        case decodingFailed = "decodingFailed"
        case requestFailed = "requestFailed"
        case invalidResponse = "invalidResponse"
    }
    

    Note, there is no need for using the addingPercentEncoding, you could just have this:

    let endpoint = "(baseURL)/(artist)/(title)"
    guard let url = URL(string: endpoint) else {
        throw NetworkError.invalidURL
    }
    do {
        let (data, response) = try await URLSession.shared.data(from: url)
     
    
    Login or Signup to reply.
  2.     let artist = "Burma Boy"
        let title = "City Boy"
        let url = artist + "/" + title
        let u = URL(string: url)
        print(u?.absoluteString)
    
        outputs Optional("Burma%20Boy/City%20Boy")
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search