Swift Wordpress Posts Fetch Page - ios

I'm a beginner for programming and swift. I'm developing an app for my small business. It's almost finished but i'm completely stucked with blog page. I just want to fetch data from my website which is wordpress, and put my posts in to my tableview. I was searching but couldn't find a proper answer. Please at least me tell me where to begin with
Thank you

You need to use awesome new API of WordPress called WP REST API, which will give you all data you need in JSON format. Have a look at this url to get started:
http://v2.wp-api.org/
Use this official plugin in your WordPress site:
https://wordpress.org/plugins/rest-api/
Update: You do not need to install above plugin as it has been merged into WordPress core from v4.4 to onwards.

At first, you should define models for parsing the JSON response, for example, as below.
import Foundation
struct Title: Decodable {
let rendered: String
}
struct Content: Decodable {
let rendered: String
}
struct WPFeaturedMedia: Decodable {
let sourceURLString: String
var url: URL? {
.init(string: sourceURLString)
}
enum CodingKeys: String, CodingKey {
case sourceURLString = "source_url"
}
}
struct Embedded: Decodable {
let medias: [WPFeaturedMedia]?
enum CodingKeys: String, CodingKey {
case medias = "wp:featuredmedia"
}
}
struct PostHeader: Decodable, Identifiable {
let id: Int
let date: Date
let title: Title
let embedded: Embedded
enum CodingKeys: String, CodingKey {
case date = "date"
case title = "title"
case embedded = "_embedded"
case id = "id"
}
}
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"
}
}
You can easily fetch posts using Combine.
import Combine
import SwiftUI
final class ViewModel: ObservableObject {
#Published var postHeaders: [PostHeader] = []
#Published var error: Error?
private var cancellable: AnyCancellable?
init() {
let url = URL(string: "https://PUT_YOUR_HOST_HERE/wp-json/wp/v2/posts?_fields=id,date,title,_links,_embedded&_embed”)!
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: [PostHeader].self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
switch completion {
case .failure(let error):
self.error = error
case .finished:
break
}
},
receiveValue: { posts in
self.postHeaders = posts
}
)
}
}
Full sources you can find here: https://github.com/fuzzzlove/SwiftUIWordpressClient

Related

How to determine and set the type of Object of a parameter from a different parameter in JSON in Swift?

How can I implement a solution where I could set the type of a parameter on the go while parsing a JSON by analyzing the parameter at the same level as the other parameter?
let sampleData = Data("""
[
{
"type": "one",
"body": {
"one": 1
},
.
.
.
},
{
"type": "two",
"body": {
"two": 2,
"twoData": "two"
},
.
.
.
}
]
""".utf8)
struct MyObject: Codable {
let type: String
let body: Body
}
struct Body: Codable {
let one, two: Int?
let twoData: String?
}
print(try JSONDecoder().decode([MyObject].self, from: sampleData))
Here you can see that the keys in Body are all optionals. My solution requires they being parsed into different types according to the value given in the parameter type. How can I parse body into the following 2 separate types according to the value I receive in type?
struct OneBody: Decodable {
let one: Int
}
struct TwoBody: Decodable {
let two: Int
let twoData: String
}
The approach that I finally went with is with a custom init(from decoder: Decoder) method where the type is determined first and then using a switch statement iterate through each case to decode each type and assign it to the body as another enum. Here is the code:
struct MyObject: Decodable {
let body: MyBody
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(MyType.self, forKey: .type)
switch type {
case .one:
body = .one(try container.decode(OneBody.self, forKey: .body))
case .two:
body = .two(try container.decode(TwoBody.self, forKey: .body))
}
}
enum CodingKeys: String, CodingKey {
case type, body
}
}
enum MyType: String, Decodable {
case one, two
}
enum MyBody: Decodable {
case one(OneBody)
case two(TwoBody)
}
struct OneBody: Decodable {
let one: Int
}
struct TwoBody: Decodable {
let two: Int
let twoData: String
}
print(try JSONDecoder().decode([MyObject].self, from: sampleData))
PS: Please post an answer or a comment if there are any ideas for a better approach.

Wordpress REST API + Swift

I'm a novice developer trying to write a simple app that presents posts from a Wordpress site as a feed. I'm using the Wordpress REST API and consuming that within swift. I'm getting stuck at parsing the JSON and presenting it in swift.
Detail below, but how do I code the dual identifier of 'title' + 'rendered' from the REST API?
So far I've got this in swift:
import SwiftUI
struct Post: Codable, Identifiable {
let id = UUID()
var title.rendered: String
var content.rendered: String
}
class Api {
func getPosts(completion: #escaping ([Post]) -> ()) {
guard let url = URL(string: "https://councillorzamprogno.info/wp-json/wp/v2/posts") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let posts = try! JSONDecoder().decode([Post].self, from: data)
DispatchQueue.main.async {
completion(posts)
}
}
.resume()
}
but the "var title.rendered: String" isn't accepted by Xcode, I get the error "Consecutive declarations on a line must be seperated by ';'. So how should I go about getting the post tile, content etc. when it appears like this in the REST API:
{
id: 1216,
date: "2020-11-18T00:51:37",
date_gmt: "2020-11-17T13:51:37",
guid: {
rendered: "https://councillorzamprogno.info/?p=1216"
},
modified: "2020-11-18T01:31:52",
modified_gmt: "2020-11-17T14:31:52",
slug: "the-nsw-2020-state-redistribution",
status: "publish",
type: "post",
link: "https://councillorzamprogno.info/2020/11/18/the-nsw-2020-state-redistribution/",
title: {
rendered: "The NSW 2020 State Redistribution"
},
content: {
rendered: " <figure class="wp-block-embed is-type-video is-provider-youtube
(etc.)
Create another Codable type as below and update Post,
struct Rendered: Codable {
var rendered: String
}
struct Post: Codable, Identifiable {
let id = UUID()
var title: Rendered
var content: Rendered
}

SwiftUI List doesn't appear

Good morning,
I have an issue with my SwiftUI list.
After receiving the data from my JSON correctly and having it as a string afterwards, the informations doesn't seem to appear in my list view.
struct Lists: View{
#State private var Countries = [Country]()
var body: some View {
List(Countries, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.Country)
.font(.headline)
Text(item.Country)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.covid19api.com/summary") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
let jsonData = (try! String(contentsOf: url))
/*print(jsonData)*/
URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let jsonData = data {
do {
let decodedResponse = try decoder.decode(AllCountries.self, from: jsonData)
print(decodedResponse.Countries)
} catch let error as NSError {
/*print("json error: \(error.localizedDescription)")*/
print("json error: \(error)")
}
}
}.resume()
}
Here are my structs for the object:
struct AllCountries: Decodable {
var Countries: [Country]
}
struct AllCountries: Decodable {
var Countries: [Country] }
struct Country: Decodable, Identifiable {
let id = UUID()
var Country, CountryCode, Slug: String
var NewConfirmed, TotalConfirmed, NewDeaths, TotalDeaths: Int
var NewRecovered, TotalRecovered: Int
var Date: Date
}
enum CodingKeys: String, CodingKey {
case Country = "Country"
case CountryCode = "CountryCode"
case Slug = "Slug"
case NewConfirmed = "NewConfirmed"
case TotalConfirmed = "TotalConfirmed"
case NewDeaths = "NewDeaths"
case TotalDeaths = "TotalDeaths"
case NewRecovered = "NewRecovered"
case TotalRecovered = "TotalRecovered"
case Date = "Date"
}
}
Here is the beginning of the result of the "data" when I print it:
[_IOS_Project.Country(id: EB629D42-8278-444C-878E-A6EAC46BD5D6, Country: "Afghanistan", CountryCode: "AF", Slug: "afghanistan", NewConfirmed: 546, TotalConfirmed: 28424, NewDeaths: 21, TotalDeaths: 569, NewRecovered: 330, TotalRecovered: 8292, Date: 2020-06-21 19:37:01 +0000), _IOS_Project.Country(id: 8DDDCA84-CE99-4374-A487-096BFDF8A467, Country: "Albania", CountryCode: "AL", Slug: "albania", NewConfirmed: 53, TotalConfirmed: 1891, NewDeaths: 1, TotalDeaths: 43, NewRecovered: 12, TotalRecovered: 1126, Date: 2020-06-21 19:37:01 +0000),
Could somebody point me to the right direction on this issue?
Thanks in advance :)
There seems to be a few problems with your code.
Firstly the naming of your variables. Variable names in Swift begin with a lowercase, structs and classes begin with uppercase.
Secondly you aren't assigning the response from the your URLRequest to the countries state variable, this is the main problem.
Thirdly, your pasted code doesn't seem to be formatted correctly.
I put your code into a fresh project and with a few tweaks I got it to work.
struct ContentView: View {
#State private var countries = [AllCountries.Country]()
var body: some View {
List(countries, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.country)
.font(.headline)
Text(item.country)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.covid19api.com/summary") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let jsonData = data {
do {
let decodedResponse = try decoder.decode(AllCountries.self, from: jsonData)
print(decodedResponse.countries)
// You should update this on the main thread
DispatchQueue.main.async {
// this assigns the values you received to the state variable countries
self.countries = decodedResponse.countries
}
} catch let error as NSError {
print("json error: \(error)")
}
}
}.resume()
}
}
Notice in the loadData function that I assign the response from the URLRequest to the countries variable. Because this is a #State variable it causes the screen to reload when it changes. You weren't doing this so your UI had no idea that it needed to update.
I also updated your variable names, so that they are lowercased.
struct AllCountries: Decodable {
var countries: [Country]
enum CodingKeys: String, CodingKey {
case countries = "Countries"
}
struct Country: Decodable, Identifiable {
let id = UUID()
var country, countryCode, slug: String
var newConfirmed, totalConfirmed, newDeaths, totalDeaths: Int
var newRecovered, totalRecovered: Int
var date: Date
enum CodingKeys: String, CodingKey {
case country = "Country"
case countryCode = "CountryCode"
case slug = "Slug"
case newConfirmed = "NewConfirmed"
case totalConfirmed = "TotalConfirmed"
case newDeaths = "NewDeaths"
case totalDeaths = "TotalDeaths"
case newRecovered = "NewRecovered"
case totalRecovered = "TotalRecovered"
case date = "Date"
}
}
}

JSON objects in Swift 5

I'm trying to access data in a json api like below
"Products": [
{
"ProductName": "GR",
"ShortDescription": "General service epoxy mortar system that utilizes recycled glass and rapidly renewable soy based components.",
"PDSOverride": [
{
"FileName": "EcoLab Netherlands",
"FileUrl": "http://test.stonhard.com/media/2264/eco-lab-netherlands-usa-version.pdf"
},
{
"FileName": "General Dynamics.pdf",
"FileUrl": "http://test.stonhard.com/media/2060/general-dynamics.pdf"
}
]
}
]
And I'm modeling this in a struct like this below
struct Solutions: Codable, Identifiable {
let id = UUID()
let SectionTitle: String
let SectionImage: String
let ProductLines: [ProductLine]
}
struct ProductLine: Codable {
let System: String
let Products: [Product]
}
struct Product: Codable {
let ProductName: String
let ShortDescription: String
let PDSOverride: [PDSOverride]
}
struct PDSOverride: Codable {
let FileName: String
let FileUrl: String
}
struct SdsPdf: Codable {
let FileName: String
let FileUrl: String
}
struct GuideSpecPdf: Codable {
let FileName: String
let FileUrl: String
}
When I try to access the data, I get an error that says The data couldn't be read because it isn't in the correct format. I know it's a problem with my model because when I comment out PDSOverride, SdsPdf, and GuideSpecPdf it works, but obviously I don't have access to that data. How do I model my struct so I can pull in that data?
The problem is not in your model, your JSON is bad formatted as it says by the compiler your JSON needs to look like this:
[
{
"Products": [
{
"ProductName": "GR",
"ShortDescription": "General service epoxy mortar system that utilizes recycled glass and rapidly renewable soy based components.",
"PDSOverride": [
{
"FileName": "EcoLab Netherlands",
"FileUrl": "http://test.stonhard.com/media/2264/eco-lab-netherlands-usa-version.pdf"
},
{
"FileName": "General Dynamics.pdf",
"FileUrl": "http://test.stonhard.com/media/2060/general-dynamics.pdf"
}
]
}
]
}
]
Also, the model could be done in this way, I recommend you to use Quicktype (https://app.quicktype.io) the online version is good and they have a desktop one:
// MARK: - PurpleProduct
struct PurpleProduct: Codable {
let products: [ProductProduct]
enum CodingKeys: String, CodingKey {
case products = "Products"
}
}
// MARK: - ProductProduct
struct ProductProduct: Codable {
let productName, shortDescription: String
let pdsOverride: [PDSOverride]
enum CodingKeys: String, CodingKey {
case productName = "ProductName"
case shortDescription = "ShortDescription"
case pdsOverride = "PDSOverride"
}
}
// MARK: - PDSOverride
struct PDSOverride: Codable {
let fileName: String
let fileURL: String
enum CodingKeys: String, CodingKey {
case fileName = "FileName"
case fileURL = "FileUrl"
}
}
typealias Products = [PurpleProduct]
And to decoded you can use the JSONDecoder, I also recommend you to check your json in this page: https://jsonformatter.curiousconcept.com
Assuming your JSON is only missing the closing braces, you can parse it like so:
struct Entry: Codable {
let products: [Product]
enum CodingKeys: String, CodingKey {
case products = "Products"
}
}
struct Product: Codable {
let productName, shortDescription: String
let pdsOverride: [PDSOverride]
enum CodingKeys: String, CodingKey {
case productName = "ProductName"
case shortDescription = "ShortDescription"
case pdsOverride = "PDSOverride"
}
}
struct PDSOverride: Codable {
let fileName: String
let fileURL: String
enum CodingKeys: String, CodingKey {
case fileName = "FileName"
case fileURL = "FileUrl"
}
}
do {
let entries = try JSONDecoder().decode([Entry].self, from: data)
} catch {
print(error)
}

How to Decode Nested dictionary with nested arrays using Swift Decodable property?

The actual JSON,that I need to parse in swift4 is,
{
"class": {
"semester1": [
{
"name": "Kal"
},
{
"name": "Jack"
},
{
"name": "Igor"
}
],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept",
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
The code that I'd tried but failed to execute is,
struct Class: Decodable {
let semester: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case location = "location"
case statusTracker = "statusTracker"
}
}
struct location: Decodable {
let Dept: [typesSubIn]
let BlockNo: Int
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct internalComponents: Decodable {
let semester1: [semsIn]
let subjects: [subjectsIn]
}
struct semsIn: Decodable {
let nameIn: String
}
struct subjectsIn: Decodable {
let subjects: String
}
struct Dept: Decodable {
let Depts: String
}
I know it's completely wrong can someone give the actual format? I'm actually confused with the format for "subjects".It's not compiling as a whole too.
There are many issues.
You are making a common mistake by ignoring the root object partially.
Please take a look at the JSON: On the top level there are 3 keys class, location and statusTracker. The values for all 3 keys are dictionaries, there are no arrays.
Since class (lowercase) is a reserved word, I'm using components. By the way please conform to the naming convention that struct names start with a capital letter.
struct Root : Decodable {
let components : Class
let location: Location
let statusTracker: StatusTracker
enum CodingKeys: String, CodingKey { case components = "class", location, statusTracker }
}
There are many other problems. Here a consolidated version of the other structs
struct Class: Decodable {
let semester1: [SemsIn]
let subjects : [String]
}
struct Location: Decodable {
let dept : [String]
let blockNo : Int
enum CodingKeys: String, CodingKey { case dept = "Dept", blockNo = "BlockNo" }
}
struct SemsIn: Decodable {
let name: String
}
struct StatusTracker: Decodable {
let googleFormsURL: String // URL is no benefit
let totalCount: Int
}
Now decode Root
do {
let result = try decoder.decode(Root.self, from: data)
} catch { print(error) }
It looks like you sterilize Class object in wrong way. It should looks like:
struct Class: Decodable {
let class: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
}
There are a few things here causing your issue.
You have no top level item, I added Response struct
Location, class and statusTracker are both at the same level, not under class.
In your class struct, your items are set as arrays but they aren't arrays
To debug these types of issues, wrap your decode in a do catch block and print out the error. it will tell you the reason it failed to parse
Try this code from my playground:
let jsonData = """
{
"class": {
"semester1": [{
"name": "Kal"
}, {
"name": "Jack"
}, {
"name": "Igor"
}],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept"
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
""".data(using: .utf8)!
struct Response: Decodable {
let cls: Class
let location: Location
let statusTracker: statusTracker
enum CodingKeys: String, CodingKey {
case cls = "class"
case location
case statusTracker
}
}
struct Class: Decodable {
let semester: [SemesterStudents]
let subjects: [String]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case subjects
}
}
struct Location: Decodable {
let dept: [String]
let blockNo: Int
enum CodingKeys: String, CodingKey {
case dept = "Dept"
case blockNo = "BlockNo"
}
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct SemesterStudents: Decodable {
let name: String
}
struct Dept: Decodable {
let Depts: String
}
do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch let error {
print(error)
}
Another approach is to create an intermediate model that closely matches the JSON, let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:
// snake_case to match the JSON
fileprivate struct RawServerResponse: Decodable {
struct User: Decodable {
var user_name: String
var real_info: UserRealInfo
}
struct UserRealInfo: Decodable {
var full_name: String
}
struct Review: Decodable {
var count: Int
}
var id: Int
var user: User
var reviews_count: [Review]
}
struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int
init(from decoder: Decoder) throws {
let rawResponse = try RawServerResponse(from: decoder)
// Now you can pick items that are important to your data model,
// conveniently decoded into a Swift structure
id = String(rawResponse.id)
username = rawResponse.user.user_name
fullName = rawResponse.user.real_info.full_name
reviewCount = rawResponse.reviews_count.first!.count
}
}

Resources