I have a WordPress post and this contains a post picture that is updated regularly. I would like to dynamically query this post picture in Swift but have not yet found a solution for it.
Could someone help me?
Here is my code for which I am requesting a regular image at the moment:
var coursesPicture = [CoursesPicture]()
// MARK: - GUID
struct GUIDURL: Codable {
let rendered: String
}
// MARK: - Courses
struct Course: Codable {
let guid, title: GUID
let content: Content
let links: Links
enum CodingKeys: String, CodingKey {
case guid, title, content
case links = "_links"
}
}
// MARK: - Content
struct Content: Codable {
let rendered: String
let protected: Bool
}
// MARK: - GUID
struct GUID: Codable {
let rendered: String
}
// MARK: - Links
struct Links: Codable {
}
// MARK: - CoursePicture
struct CoursesPicture: Codable {
let featuredMedia: Int
enum CodingKeys: String, CodingKey {
case featuredMedia = "featured_media"
}
}
public func fetchJSONPicture() {
let urlString = "https://ismaning.de/wp-json/wp/v2/media/4291"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.sync {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
self.coursesPicture = [try JSONDecoder().decode(CoursesPicture.self, from: data)]
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}
if let url = URL(string: "https://ismaning.de/wp-content/uploads/ismaning-willkommen-banner_app-ios.jpg") {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
DispatchQueue.main.sync {
self.imageView.image = UIImage(data: data)
}
}
}.resume()
}
}
I simply query the image here via a URL. Nevertheless, it should be queried dynamically via a WordPress post. I also tried this JSON query but unfortunately I haven't found a solution yet
Thank you in advance for your help, as I have been looking for a solution for a while now
After discussion in the chat we found a working solution for this problem. If anyone else has this you can simply do this:
Create two structs, example Struct Response: Codable{}
Create two arrays which are of the struct types like: var arr:[Response] = []
In the ViewDidLoad you will call the getJsonData method with completion block.
When the data is loaded in the first array, you call the getJsonImage method with completion block.
Once you have the imageURL you can simply retrieve it by creating a URLSession to retrieve the image.
This is the solution code:
//Arrays
var firstArray:[FirstResponse] = []
var imgData:[ImgData] = []
override func viewDidLoad() {
super.viewDidLoad()
//Fetching the JSON data
fetchJSON {
//Fetching the JSON image
self.fetchJSONImage {
let imageUrl = self.imgData[0].link
self.imgView.downloaded(from: imageUrl)
}
}
}
//Retrieving the JSON data
func fetchJSON(completed: #escaping () -> ()){
var urlString = "HIDDEN"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
guard let url = URL(string: urlString) else {return}
//Create the URLSession
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else{
return
}
do{
//Sets the dataArray to JSON data
self.firstArray = [try JSONDecoder().decode(FirstResponse.self, from: data)]
//Complete task in background
DispatchQueue.main.async {
completed()
}
}
catch let jsonErr{
print(jsonErr)
}
}.resume()
}
//Retrieving the image link from JSON
func fetchJSONImage(completed: #escaping () -> ()){
let imgPath = firstArray[0].featured_media
var urlString = "HIDDEN-URL\(imgPath)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
guard let url = URL(string: urlString) else {return}
//Create the URLSession
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else{
return
}
do{
//Sets the dataArray to JSON data
self.imgData = [try JSONDecoder().decode(ImgData.self, from: data)]
//Complete task in background
DispatchQueue.main.async {
completed()
}
}
catch let jsonErr{
print(jsonErr)
}
}.resume()
}
}
//Struct for getting image ID
struct FirstResponse: Codable{
let featured_media: Int
}
//Struct for getting the image link
struct ImgData: Codable{
let link: String
}
//Extension
extension UIImageView {
//Download the image from the API
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleToFill) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard let image = UIImage(data: data)
else { return }
//Continue in background
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
}
}
Related
I am trying to query the marvel API. I believe my decodable is wrong. I have the following code:
struct ReturnedData : Decodable {
var id : Int?
var name : String?
var description : String?
}
var savedData : [ReturnedData] = []
let urlString = "https://gateway.marvel.com/v1/public/characters?ts=1&apikey=\(myAPIKey)"
let url = URL(string: urlString)
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) { (data, response, error) in
guard let data = data else {return}
do {
let recievedData = try JSONDecoder().decode([ReturnedData].self, from: data)
self.savedData = recievedData
} catch {
print(error)
}
}
dataTask.resume()
}
I am getting the following error message:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
according to the documentation, I should get all of the below:
Character
id (int, optional): The unique ID of the character resource.,
name (string, optional): The name of the character.
description (string, optional): A short bio or description of the character.
modified (Date, optional): The date the resource was most recently modified.
resourceURI (string, optional): The canonical URL identifier for this resource.
urls (Array[Url], optional): A set of public web site URLs for the resource.
thumbnail (Image, optional): The representative image for this character.
comics (ComicList, optional): A resource list containing comics which feature this character.
stories (StoryList, optional): A resource list of stories in which this character appears
events (EventList, optional): A resource list of events in which this character appears.
series (SeriesList, optional): A resource list of series in which this character appears.
Also, any tips on how to get the thumbnail image is appreciated.
The error you get is because the model you have does not match the json data. So, try this approach ...to query the marvel API....
In future, copy and paste your json data into https://app.quicktype.io/
this will generate the models for you. Adjust the resulting code to your needs. Alternatively read the docs at: https://developer.marvel.com/docs#!/public/getCreatorCollection_get_0 and create the models by hand from the info given.
class MarvelModel: ObservableObject {
#Published var results = [MarvelResult]()
let myAPIKey = "xxxx"
let myhash = "xxxx"
func getMarvels() async {
guard let url = URL(string: "https://gateway.marvel.com/v1/public/characters?ts=1&apikey=\(myAPIKey)&hash=\(myhash)") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let response = try JSONDecoder().decode(MarvelResponse.self, from: data)
Task{#MainActor in
results = response.data.results
}
} catch {
print("---> error: \(error)")
}
}
}
struct ContentView: View {
#StateObject var vm = MarvelModel()
var body: some View {
VStack {
if vm.results.isEmpty { ProgressView() }
List(vm.results) { item in
Text(item.name)
}
}
.task {
await vm.getMarvels()
}
}
}
struct MarvelResponse: Decodable {
let code: Int
let status, copyright, attributionText, attributionHTML: String
let etag: String
let data: MarvelData
}
struct MarvelData: Decodable {
let offset, limit, total, count: Int
let results: [MarvelResult]
}
struct MarvelResult: Identifiable, Decodable {
let id: Int
let name, resultDescription: String
let modified: String
let thumbnail: Thumbnail
let resourceURI: String
let comics, series: Comics
let stories: Stories
let events: Comics
let urls: [URLElement]
enum CodingKeys: String, CodingKey {
case id, name
case resultDescription = "description"
case modified, thumbnail, resourceURI, comics, series, stories, events, urls
}
}
struct Comics: Decodable {
let available: Int
let collectionURI: String
let items: [ComicsItem]
let returned: Int
}
struct ComicsItem: Identifiable, Decodable {
let id = UUID()
let resourceURI: String
let name: String
}
struct Stories: Decodable {
let available: Int
let collectionURI: String
let items: [StoriesItem]
let returned: Int
}
struct StoriesItem: Identifiable, Decodable {
let id = UUID()
let resourceURI: String
let name: String
let type: String
}
struct Thumbnail: Decodable {
let path: String
let thumbnailExtension: String
enum CodingKeys: String, CodingKey {
case path
case thumbnailExtension = "extension"
}
}
struct URLElement: Identifiable, Decodable {
let id = UUID()
let type: String
let url: String
}
EDIT-1: if you want something very basic, then try this:
struct ContentView: View {
#State var results = [MarvelResult]()
var body: some View {
List(results) { item in
Text(item.name)
}
.onAppear {
getMarvels()
}
}
func getMarvels() {
let myAPIKey = "xxxx"
let myhash = "xxxx"
guard let url = URL(string: "https://gateway.marvel.com/v1/public/characters?ts=1&apikey=\(myAPIKey)&hash=\(myhash)") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let response = try JSONDecoder().decode(MarvelResponse.self, from: data)
DispatchQueue.main.async {
self.results = response.data.results
}
}
catch {
print(error)
}
}
}.resume()
}
}
I would like to return the var points from my method getAllPoints()
struct tempPoint: Decodable {
let idPoint:Int
let ptName:String
let ptLat:Double
let ptLong:Double
}
func getAllPoints(){
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
let jsonData = Data(jsonString.utf8)
do {
let points = try JSONDecoder().decode([tempPoint].self, from: jsonData)
} catch let error {
print(error)
}
print(jsonString)
}
}
}.resume()
}
}
Results that I got when I do print(points)
[
{"idPoint":6,"ptName":"Maison","ptLat":43.72623997050257,"ptLong":2.202591651830584},
{"idPoint":7,"ptName":"Wheelin","ptLat":42.75754326128173,"ptLong":8.137330631668685},
{"idPoint":8,"ptName":"Animoz","ptLat":45.76321863196126,"ptLong":7.137186047202841},
{"idPoint":9,"ptName":"Hesias","ptLat":45.767222865412144,"ptLong":6.132352002277385},
{"idPoint":10,"ptName":"Marcombes","ptLat":45.76018235160473,"ptLong":4.085466264251463},
{"idPoint":11,"ptName":"Leclan","ptLat":48.80950120948317,"ptLong":2.110623123039061},
{"idPoint":12,"ptName":"Cournon Skatepark","ptLat":39.74138613175866,"ptLong":4.2154977334348906}
]
I wonder if this is possible to return these at the end of my method.
I'm gonna use it in a pickerView and to do a CRUD that's why I would like to store them.
Thank You
use completion closure to "return" your points.
Try something like this:
func getAllPoints(completion: #escaping([tempPoint]) -> ()) { // <-- here
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let points = try JSONDecoder().decode([tempPoint].self, from: data)
completion(points) // <-- here return the results when done
return
} catch let error {
print(error) // <-- here todo deal with errors
}
}
completion([]) // <-- here todo deal with errors
}.resume()
} else {
completion([]) // <-- here todo deal with errors
}
}
and call the function like this:
getAllPoints() { results in
print("array of points: \(results)")
}
My understanding is that you wanted the api response at the position of calling your function. You can return your response using closures.
The typealias you are seeing in the below code are self defined data types. You can directly add closures in your function but to make it more simple for you I declare those closures with typealias as data types. You can read more about typealias and closures here.
After declaring our own closure types. We need to use them in our function as parameters.
struct TempPoint: Decodable {
let idPoint:Int
let ptName:String
let ptLat:Double
let ptLong:Double
}
typealias ApiResponse = ([TempPoint]) -> Void
typealias ApiError = (Error) -> Void
func getAllPoints(_ completion: #escaping ApiResponse, apiError: #escaping ApiError){
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
let jsonData = Data(jsonString.utf8)
do {
let points = try JSONDecoder().decode([TempPoint].self, from: jsonData)
completion(points)
} catch let error {
print(error)
apiError(error)
}
print(jsonString)
}
}
}.resume()
}
}
Our function after changes would look like above.
Now here is how we can use this function.
override func viewDidLoad() {
super.viewDidLoad()
getAllPoints { [weak self] (points) in
// you update you UI here, be sure to call in main thread when you are doing UI updates in closures.
print(points)
} apiError: { (error) in
print(error)
}
}
It would be efficient if things are loosely coupled which can be done with Delegate pattern.
protocol TempPointDelegate {
func didUpdatePoints(_ tempPoints: [tempPoint])
func didFailWithError(error: Error)
}
and in tempPoint Struct
struct tempPoint: Decodable {
let idPoint:Int
let ptName:String
let ptLat:Double
let ptLong:Double
}
var delegate: TempPointDelegate?
func getAllPoints(){
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
let jsonData = Data(jsonString.utf8)
do {
let points = try JSONDecoder().decode([tempPoint].self, from: jsonData)
self.delegate?.didUpdatePoints(points)
return
} catch let error {
self.delegate?.didFailWithError(error)
return
}
}
}
}.resume()
}
}
finally in your ViewController implement tempPointDelegate delegate
class MainViewController: tempPointDelegate{
override func viewDidLoad() {
super.viewDidLoad()
tempPoint.getAllPoints()
}
func didUpdatePoints(_ tempPoints: [tempPoint]) {
DispatchQueue.main.async {
//process the tempPoints here / call the func to handle tempPoints
}
}
func didFailWithError(error: Error) {
//process the error returned here.
}
}
First of all please name structs with starting uppercase letter (TempPoint).
Second of all the conversion to String and back to Data is pointless
In Swift 5.5 you can use async/await
func getAllPoints() async throws -> [TempPoint] {
guard let url = URL(string: "[MyWebService.com]") else { throw URLError(.badURL) }
let (data, _) = try await URLSession.shared.data(from : url)
return try JSONDecoder().decode([TempPoint].self, from: data)
}
And call it
Task {
do {
let points = try await getAllPoints()
} catch {
print(error)
}
}
I try to parse JSON with mvc pattern.I'm trying for the first time. But I'm getting "Escaping closure captures mutating 'self' parameter error " I mutated functions and put self where necessary.I can access and use required protocols in ViewController. Everything seems normal What's the problem here ?
import Foundation
protocol ItunesManagerDelegate {
func didUpdateSearchin(_ ItunesManager:ItunesManager, product: [ItunesModel])
func didFailWithError(error:Error)
}
struct ItunesManager {
let baseURL = "https://itunes.apple.com/search?term="
var delegate : ItunesManagerDelegate?
var itunesDataArray = [ItunesModel]()
mutating func getSearchResult(for terms:String){
let urlString = "\(baseURL)\(terms)"
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let searchResult = self.parseJSON(data!){
self.delegate?.didUpdateSearchin(ItunesManager(), product: searchResult)
}
}
task.resume()
}
}
mutating func parseJSON(_ itunesData:Data) -> [ItunesModel]? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([ItunesData].self, from: itunesData)
decodedData.forEach { (ItunesData) in
ItunesData.results.forEach { (SearchResults) in
let type = SearchResults.wrapperType
let name = SearchResults.collectionName
let url = SearchResults.artworkUrl100
let price = SearchResults.collectionPrice
let date = SearchResults.releaseDate
let itunesProductInfo = ItunesModel(collectionName: name, collectionPrice: price, artWorkUrl100: url, wrapperType: type, releaseDate: date)
itunesDataArray.append(itunesProductInfo)
}
}
return itunesDataArray
}catch{
delegate?.didFailWithError(error: error)
return nil
}
}
}
I'm trying to retrieve images from array of url..
I have this function that do the same as I won't but it doesn't work so I tried to use URLSession but didn't know how exactly to make it >>
func downloadImages(imageUrls: [String], completion: #escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
for link in imageUrls {
let url = NSURL(string: link)
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
downloadQueue.sync {
downloadCounter += 1
let data = NSData(contentsOf: url! as URL)
if data != nil {
//image data ready and need to be converted to UIImage
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
DispatchQueue.main.async {
completion(imageArray)
}
}
} else {
print("couldnt download image")
completion(imageArray)
}
}
}
}
The function I work on :
public func imagesFromURL(urlString: [String],completion: #escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
for link in urlString {
downloadQueue.sync {
downloadCounter += 1
let dataTask = URLSession.shared.dataTask(with: NSURL(string: link)! as URL, completionHandler: { (data, response, error ) in
if error != nil {
print(error ?? "No Error")
return
}
if data != nil {
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
completion(imageArray)
}
} else {
print("couldnt download image")
completion(imageArray)
}
} dataTask.resume()
}
}
}
i want to call the function in the collection cell and get the display the first image only from each artwork array..
//download the first image only to display it:
if artwork.ImgLink != nil && artwork.ImgLink.count > 0 {
downloadImages(imageUrls: [artwork.ImgLink.first!]) { (images) in
self.artworkImage.image = images.first as? UIImage
}
}
If you intend to use only the first available UIImage from an array of urls, you do not design a function trying to download all of them. Instead, try to download from the first url, return the downloaded UIImage if it succeeds, or continue with the second url if it fails, repeat until you get an UIImage.
Creating a DispatchQueue in a local function looks dangerous to me. A more common practice is to maintain a queue somewhere else and pass it to the function as a parameter, or reuse one of the predefined global queues using DispatchQueue.global(qos:) if you don't have a specific reason.
Be careful with sync. sync blocks the calling thread until your block finishes in the queue. Generally you use async.
Use a Int counter to control when to finish multiple async tasks (when to call the completion block) works but can be improved by using DispatchGroup, which handles multiple async tasks in a simple and clear way.
Here's two functions. Both work. firstImage(inURLs:completion:) only return the first UIImage that it downloads successfully. images(forURLs:completion:) tries to download and return them all.
func firstImage(inURLs urls: [String], completion: #escaping (UIImage?) -> Void) {
DispatchQueue.global().async {
for urlString in urls {
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
completion(image)
}
return
}
}
}
DispatchQueue.main.async {
completion(nil)
}
}
}
// Use it.
firstImage(inURLs: artwork.ImgLink) { image in
self.artworkImage.image = image
}
func images(forURLs urls: [String], completion: #escaping ([UIImage?]) -> Void) {
let group = DispatchGroup()
var images: [UIImage?] = .init(repeating: nil, count: urls.count)
for (index, urlString) in urls.enumerated() {
group.enter()
DispatchQueue.global().async {
var image: UIImage?
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
image = UIImage(data: data)
}
}
images[index] = image
group.leave()
}
}
group.notify(queue: .main) {
completion(images)
}
}
// Use it.
images(forURLs: artwork.ImgLink) { images in
self.artworkImage.image = images.first(where: { $0 != nil }) ?? nil
}
I'm getting this error when I try to proceed on parsing the JSON. Does anyone know what I gotta do to fix it?
Error 1: Cannot subscript a value of type 'AudiobookJSON' (aka 'Array<Dictionary<String, Any>>') with an index of type 'String'
Error on Print
File Model: Audiobook.swift:
import Foundation
struct Audiobook : Codable {
let id: Int
let descricao: String
let urlImagem: String
init(dictionary: AudiobookJSON) {
self.id = dictionary["id"] as! Int//////ERROR MESSAGE ////////
self.descricao = dictionary["descricao"] as! String/////ERROR MESSAGE
self.urlImagem = dictionary["urlImagem"] as! String////ERROR MESSAGE
}
}
Folder Networking: APIClient.swift:
import Foundation
typealias AudiobookJSON = [[String: Any]]
struct APIClient {
static func getAudiobooksAPI(completion: #escaping ([Audiobook]?) -> Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/categoria")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error unwrapping URL"); return }
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
//let json = try JSONSerialization.jsonObject(with: unwrappedDAta, options: .allowFragments) as! [String:Any]
//let posts = json["data"] as? AudiobookJSON
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
}
As I can see that AudiobookJSON is an array of key-value pairs that's why you are getting error: So you have to use codable like that:
First: you have to make Codable type struct like that(your codable struct variable names should be same as you are getting in response):
struct Audiobook: Codable {
let id: Int?
let descricao: String?
let urlImagem: String?
}
Second: when you get the response then parse directly using codale like that:
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
You can directly use the response like:
for audio in posts {
print("audio.id")
print("audio.descricao")
print("audio.urlImagem")
}