Swift 4 Tuple Iteration - swift

I am trying to retrieve wallpapers from Bing by json in Swift 4
https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=en-US
But images property is a tuple with another objects. I can not iterate this tuple and reach the url values so that I can collect them in an array.
(
{
bot = 1;
copyright = "For the winter solstice, Santa Fe's Farolito Walk (\U00a9 Julien McRoberts/Danita Delimont)";
copyrightlink = "http://www.bing.com/search?q=santa+fe+new+mexico&form=hpcapt&filters=HpDate:%2220181221_0800%22";
drk = 1;
enddate = 20181222;
fullstartdate = 201812210800;
hs = (
);
hsh = 6ba85f1fbab1b9a290a19af763ca404d;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181221_AdobeSantaFe%22&FORM=HPQUIZ";
startdate = 20181221;
title = "All is bright in Santa Fe";
top = 1;
url = "/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534";
wp = 1;
},
{
bot = 1;
copyright = "Nabana-no-Sato gardens at Nagashima Spa Land in Kuwana, Japan (\U00a9 Julian Krakowiak/Alamy)";
copyrightlink = "http://www.bing.com/search?q=nabana+no+sato+nagashima+spa+land&form=hpcapt&filters=HpDate:%2220181220_0800%22";
drk = 1;
enddate = 20181221;
fullstartdate = 201812200800;
hs = (
);
hsh = eda366ca6eee5c653a59d8f16a54ae63;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181220_WinterIllumination%22&FORM=HPQUIZ";
startdate = 20181220;
title = "Winter illuminations in Nabana-no-Sato";
top = 1;
url = "/az/hprichbg/rb/WinterIllumination_EN-US0071328313_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/WinterIllumination_EN-US0071328313";
wp = 0;
},
{
bot = 1;
copyright = "The Charles Bridge in Prague, Czech Republic (\U00a9 borchee/E+/Getty Images)";
copyrightlink = "http://www.bing.com/search?q=prague&form=hpcapt&filters=HpDate:%2220181219_0800%22";
drk = 1;
enddate = 20181220;
fullstartdate = 201812190800;
hs = (
);
hsh = 37c19c3f45952fd81c429e5e92c386e9;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181219_PragueChristmas%22&FORM=HPQUIZ";
startdate = 20181219;
title = "Snow falls on Bohemia";
top = 1;
url = "/az/hprichbg/rb/PragueChristmas_EN-US8649790921_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/PragueChristmas_EN-US8649790921";
wp = 1;
},
{
bot = 1;
copyright = "For the anniversary of the premiere of 'The Nutcracker,' a scene of the Moscow Ballet performing the popular dance (\U00a9 Tytus Zmijewski/Epa/Shutterstock)";
copyrightlink = "http://www.bing.com/search?q=the+nutcracker&form=hpcapt&filters=HpDate:%2220181218_0800%22";
drk = 1;
enddate = 20181219;
fullstartdate = 201812180800;
hs = (
);
hsh = b6303e82458de5692e94b26cb332fbd1;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181218_NutcrackerSeason%22&FORM=HPQUIZ";
startdate = 20181218;
title = "A holiday tradition is born";
top = 1;
url = "/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424";
wp = 0;
},
{
bot = 1;
copyright = "Wilbur Wright gliding down Big Kill Devil Hill in Kitty Hawk, North Carolina (\U00a9 Library of Congress)";
copyrightlink = "http://www.bing.com/search?q=wright+brothers+first+flight&form=hpcapt&filters=HpDate:%2220181217_0800%22";
drk = 1;
enddate = 20181218;
fullstartdate = 201812170800;
hs = (
);
hsh = 5a132d06c5e7b66f0a1ec9f434a0dca1;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181217_WrightGlider%22&FORM=HPQUIZ";
startdate = 20181217;
title = "Wright brothers fly into history";
top = 1;
url = "/az/hprichbg/rb/WrightGlider_EN-US10185286591_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/WrightGlider_EN-US10185286591";
wp = 0;
},
{
bot = 1;
copyright = "Holiday decorations on a canal in Murano, Italy (\U00a9 John Warburton-Lee/DanitaDelimont.com)";
copyrightlink = "http://www.bing.com/search?q=murano+island&form=hpcapt&filters=HpDate:%2220181216_0800%22";
drk = 1;
enddate = 20181217;
fullstartdate = 201812160800;
hs = (
);
hsh = c4cc26f7a803502632d940f9466e2b7c;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181216_MuranoChristmas%22&FORM=HPQUIZ";
startdate = 20181216;
title = "Murano aglow";
top = 1;
url = "/az/hprichbg/rb/MuranoChristmas_EN-US10759540271_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/MuranoChristmas_EN-US10759540271";
wp = 1;
},
{
bot = 1;
copyright = "The Stoneman Bridge on the Merced River in Yosemite National Park (\U00a9 Ron_Thomas/E+/Getty Images)";
copyrightlink = "http://www.bing.com/search?q=yosemite+national+park&form=hpcapt&filters=HpDate:%2220181215_0800%22";
drk = 1;
enddate = 20181216;
fullstartdate = 201812150800;
hs = (
);
hsh = 6a49f8fc62b09ce83305ac0a13000a7a;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181215_YosemiteBridge%22&FORM=HPQUIZ";
startdate = 20181215;
title = "Season of solitude in Yosemite";
top = 1;
url = "/az/hprichbg/rb/YosemiteBridge_EN-US10544416282_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/YosemiteBridge_EN-US10544416282";
wp = 1;
},
{
bot = 1;
copyright = "A female northern cardinal (\U00a9 Matthew Studebaker/Minden Pictures)";
copyrightlink = "http://www.bing.com/search?q=northern+cardinal&form=hpcapt&filters=HpDate:%2220181214_0800%22";
drk = 1;
enddate = 20181215;
fullstartdate = 201812140800;
hs = (
);
hsh = 0dcf20ffcfd5413167161ad20b412fb5;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181214_CardinalBerries%22&FORM=HPQUIZ";
startdate = 20181214;
title = "The Christmas Bird Count begins";
top = 1;
url = "/az/hprichbg/rb/CardinalBerries_EN-US11262203078_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/CardinalBerries_EN-US11262203078";
wp = 1;
}
)
How can I handle this JSON tuple in Swift 4?

That's not a tuple.
That's how a NSArray is printed (that's the description implementation result). It's more Objective-C like.
(
content...
)
With force unwrap (use of !) that I'd strongly recommend to not use in production, and use if let/guard let instead (because it will cause a crash if it fails the unwrap). I used force unwrap to be more direct, that's all, show the logic.
let dataJSON = initialJSONData
//Your JSON is a Dictionary at top level
let json = try! JSONSerialization.jsonObject(with: dataJSON, options: []) as! [String: Any]
//The value of a images key is an Array of Dictionaries
let imagesArray = json["images"] as! [[String: Any]]
let imagesURLStr = imagesArray.flatMap{ $0["url"] as? String }
Now, if you use Swift 4+, I'd recommend to use Codable:
struct Result: Codable {
let images: [Image]
struct Image: Codable {
let url: String
}
}
To call it:
let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Result.self, from: dataJSON)
let images = result.images
let imagesURLStr = images.map{ $0.url }
Side Note:
This code is not tested, written only here, so there might be a typo error, a minor compiler issue but it shouldn't be un-fixable.

With the help of quicktype.io, let's use these structs :
struct Result: Codable {
let images: [Image]
let tooltips: Tooltips
}
struct Image: Codable {
let startdate, fullstartdate, enddate, url: String
let urlbase, copyright: String
let copyrightlink: String
let title, quiz: String
let wp: Bool
let hsh: String
let drk, top, bot: Int
let hs: [JSONAny]
}
struct Tooltips: Codable {
let loading, previous, next, walle: String
let walls: String
}
// MARK: Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
We can get the url properties this way:
//Create the URL
guard let url = URL(string: "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=en-US") else {
fatalError("Invalid URL")
}
//Fetch the data from the URL
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) -> Void in
guard let jsonData = data, error == nil else {
fatalError("Data error")
}
let jsonDecoder = JSONDecoder()
do {
let result = try jsonDecoder.decode(Result.self, from: jsonData)
let images = result.images
let urls = images.map { $0.url }
urls.forEach { print($0) }
}
catch {
print(error)
}
}).resume()
This prints:
/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534_1920x1080.jpg
/az/hprichbg/rb/WinterIllumination_EN-US0071328313_1920x1080.jpg
/az/hprichbg/rb/PragueChristmas_EN-US8649790921_1920x1080.jpg
/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424_1920x1080.jpg
/az/hprichbg/rb/WrightGlider_EN-US10185286591_1920x1080.jpg
/az/hprichbg/rb/MuranoChristmas_EN-US10759540271_1920x1080.jpg
/az/hprichbg/rb/YosemiteBridge_EN-US10544416282_1920x1080.jpg
/az/hprichbg/rb/CardinalBerries_EN-US11262203078_1920x1080.jpg

Related

Realm accessed from incorrect thread occasional

I have this function
class func addCVals(_ criteres: [[AnyHashable: Any]], _ type: String) {
DispatchQueue.global(qos: .background).async {
autoreleasepool {
if criteres.count > 0 {
if let realm = DBTools.getRealm() {
do {
try realm.transaction {
let oldValues = CriteresVal.objects(in: realm, where: "type = '\(type)'")
if oldValues.count > 0 {
realm.deleteObjects(oldValues)
}
for critere in criteres {
let cval = CriteresVal(critere, type)
if let c = cval {
realm.addOrUpdate(c)
}
}
}
} catch {
DebugTools.record(error: error)
}
realm.invalidate()
}
}
}
}
}
The request that get oldValues occasionally cause an error
Realm accessed from incorrect thread
I don't understand why as I get a new Realm before with this lines:
if let realm = DBTools.getRealm()
My function getRealm:
class func getRealm() -> RLMRealm? {
if !AppPreference.lastAccount.elementsEqual("") {
let config = RLMRealmConfiguration.default()
do {
return try RLMRealm(configuration: config)
} catch {
DebugTools.record(error: error)
DispatchQueue.main.async {
Notifier.showNotification("", NSLocalizedString("UNKNOWN_ERROR_DB", comment: ""), .warning)
}
}
}
return nil
}
CriteresVal is an RLMObject that is composed of this:
#objcMembers
public class CriteresVal: RLMObject {
dynamic var cvalId: String?
dynamic var type: String?
dynamic var text: String?
dynamic var compositeKey: String?
override public class func primaryKey() -> String {
return "compositeKey"
}
private func updatePrimaryKey() {
self.compositeKey = "\(self.cvalId ?? "")/\(self.type ?? "")"
}
required init(_ cvalue: [AnyHashable: Any]?, _ type: String) {
super.init()
if let values = cvalue {
if let cvalId = values["id"] as? String {
self.cvalId = cvalId
} else if let cvalId = values["id"] as? Int {
self.cvalId = "\(cvalId)"
}
self.type = type
if let text = values["text"] as? String {
self.text = text
}
}
updatePrimaryKey()
}
func generateDico() -> [String: Any] {
var dicoSortie = [String: Any]()
if let realm = self.realm {
realm.refresh()
}
if let value = cvalId {
dicoSortie["id"] = value
}
if let value = type {
dicoSortie["type"] = value
}
if let value = text {
dicoSortie["text"] = value
}
return dicoSortie
}
}
compositeKey is the primary key which included cvalId and type
Thanks for help.

keyNotFound(CodingKeys(stringValue: "coord", intValue: nil)

I am building a small swift weather app using the openweatherAPI and I am running into some issues trying to parse the JSON. I have used the following function to parse the get and parse the json.
Below is my weather data struct:
struct WeatherData: Codable {
let coord: Coord
let weather: [Weather]
let base: String
let main: Main
let visibility: Int
let wind: Wind
let clouds: Clouds
let dt: Int
let sys: Sys
let id: Int
let name: String
let cod: Int
}
struct Clouds: Codable {
let all: Int
}
struct Coord: Codable {
let lon, lat: Double
}
struct Main: Codable {
let temp: Double
let pressure, humidity: Int
let tempMin, tempMax: Double
enum CodingKeys: String, CodingKey {
case temp, pressure, humidity
case tempMin = "temp_min"
case tempMax = "temp_max"
}
}
struct Sys: Codable {
let type, id: Int
let message: Double
let country: String
let sunrise, sunset: Int
}
struct Weather: Codable {
let id: Int
let main, description, icon: String
}
struct Wind: Codable {
let speed: Double
let deg: Int
}
private func getWeatherData(url: String, parameters: [String : String]) {
let JsonURLString:[String: Any] = ["url": WEATHER_URL, "parameters": parameters]
print(JsonURLString)
let urlString = JsonURLString["url"] as? String
guard let url = URL(string: urlString!) else { return }
URLSession.shared.dataTask(with: url) { ( data, response, 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 {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
The exact error I am getting is the following:
longitude = -0.1337, latitude = 51.50998
["parameters": ["lat": "51.50998", "long": "-0.1337", "appid": "xxxxxxxxxxxxxxxxxx"], "url": "https://api.openweathermap.org/data/2.5/weather"]
keyNotFound(CodingKeys(stringValue: "coord", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"coord\", intValue: nil) (\"coord\").", underlyingError: nil))
I have looked at the following example and don't see how this would apply. Any help would be appreciated.
Icon is not appearing. Here is my model:
import UIKit
class WeatherDataModel {
//Declare your model variables here
var temperature: Int = 0
var condition: Int = 0
var city: String = ""
var weatherIconName = ""
var description: String = ""
//This method turns a condition code into the name of the weather condition image
func updateWeatherIcon(condition: Int) -> String {
switch (condition) {
case 0...300 :
return "tstorm1"
case 301...500 :
return "light_rain"
case 501...600 :
return "shower3"
case 601...700 :
return "snow4"
case 701...771 :
return "fog"
case 772...799 :
return "tstorm3"
case 800 :
return "sunny"
case 801...804 :
return "cloudy2"
case 900...903, 905...1000 :
return "tstorm3"
case 903 :
return "snow5"
case 904 :
return "sunny"
default :
return "dunno"
}
}
}
I have added my own icons. I have added this in the do catch block.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.weatherData.weatherIconName = WeatherDataModel.updateWeatherIcon(self.weatherData.condition)
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
The error I am getting this error now:
Instance member 'updateWeatherIcon' cannot be used on type 'WeatherDataModel'; did you mean to use a value of this type instead?
You are creating only the openweathermap URL but you ignore the parameters.
Use something like this for example URLComponents and URLQueryItem to build the URL query properly
private func getWeatherData(parameters: [String : String]) {
guard let lat = parameters["lat"],
let long = parameters["long"],
let appID = parameters["appid"] else { print("Invalid parameters"); return }
var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")!
let queryItems = [URLQueryItem(name: "lat", value: lat),
URLQueryItem(name: "lon", value: long),
URLQueryItem(name: "appid", value: appID)]
urlComponents.queryItems = queryItems
guard let url = urlComponents.url else { return }
URLSession.shared.dataTask(with: url) { ( data, response, err ) in
DispatchQueue.main.async { // never, never, never sync !!
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
and pass only
["lat": "51.50998", "long": "-0.1337", "appid": "xxxxxxxxxxxxxxxxxx"]
as parameters.

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

SWIFT4 Contextual type 'FPChat!.Type' cannot be used with dictionary literal

I need to initialize an object, and pass it through a prepareforsegue to another class.
Last line of the code below throws "Contextual type 'FPChat!.Type' cannot be used with dictionary literal"
if (segue.identifier == "chatmessages") {
let vc = segue.destination as! FPChatMessageViewController
//vc.currentChat = fPChat
}
}
fPchat = FPChat?
// Start the Chat
#IBAction func Chat(_ sender: UIButton) {
// Create a new entry in chats. This variable is passed with prepareforsegue
let chatRef = ref.child("chats").childByAutoId()
let chatId = chatRef.key
//fPchat = FPChat?
let fPchat = FPChat.currentChat(currentChatID: chatId)
Below chat class:
import Firebase
class FPChat {
var chatID = ""
var chatDate: Date!
var text = ""
var messages: [FPChatMessage]!
var author: FPUser!
var mine = true
// Calling FPChat.currentChat(id) I have back the FPChat object
static func currentChat(currentChatID: String) -> FPChat {
return FPChat(chatID: currentChatID)
}
private init(chatID: String) {
self.chatID = chatID
}
init(snapshot: DataSnapshot, andMessages messages: [FPChatMessage]) {
guard let value = snapshot.value as? [String: Any] else { return }
self.chatID = snapshot.key
if let text = value["text"] as? String {
self.text = text
}
guard let timestamp = value["timestamp"] as? Double else { return }
self.chatDate = Date(timeIntervalSince1970: (timestamp / 1_000.0))
guard let author = value["author"] as? [String: String] else { return }
self.author = FPUser(dictionary: author)
self.messages = messages
self.mine = self.author.userID == Auth.auth().currentUser?.uid
}
}
What I am doing wrong?

swift CGPDFDocument parsing

I'm trying to use Swift to parse the contents of PDF documents, following Apple's programming guide (in which all the examples are ObjC...)
let filepath = "/Users/ben/Desktop/Test.pdf"
let localUrl = filepath as CFString
if let pdfURL = CFURLCreateWithFileSystemPath(nil, localUrl, CFURLPathStyle.cfurlposixPathStyle, false) {
if let pdf = CGPDFDocument(pdfURL) {
if let inf = pdf.info {
CGPDFDictionaryApplyFunction(inf, { (key, object, info) -> Void in
print("\(key), \(object), \(info)")
}, nil)
}
if let cat = pdf.catalog {
CGPDFDictionaryApplyFunction(cat, { (key, object, info) -> Void in
print("\(key), \(object), \(info)")
}, nil)
}
}
}
While this seems to produce some results, it's just strings of hex digits.
0x00007ff29f43ce00, 0x00007ff29f492bd0, nil
0x00007ff29f443b60, 0x00007ff29f492cd0, nil
0x00007ff29f482590, 0x00007ff29f492dd0, nil
0x00007ff29f482a40, 0x00007ff29f492ed0, nil
0x00007ff29f482e30, 0x00007ff29f492fe0, nil
0x00007ff29f47da20, 0x00007ff29f4930e0, nil
0x00007ff29f474ac0, 0x00007ff29f842b50, nil
0x00007ff29f43f5d0, 0x00007ff29f842bf0, nil
0x00007ff29f485eb0, 0x00007ff29f842a60, nil
0x00007ff29f482f70, 0x00007ff29f842ab0, nil
0x00007ff29f48b1c0, 0x00007ff29f48f6d0, nil
So how do I get the actual data? Ideally, I'm trying to get at the document metadata and things like fonts contained.
Swift 4 - Here is an updated version of Daniel's excellent example which compiles in Swift 4.
import Foundation
import Quartz
print("Hello, World!")
func printPDFKeys( key: UnsafePointer<Int8>, object: CGPDFObjectRef) { //, info: UnsafeMutableRawPointer) {
// let _: CGPDFDictionaryRef = CGPDFDictionaryRef(info)
let keyString = String(cString: UnsafePointer<CChar>(key), encoding: .isoLatin1)
let objectType = CGPDFObjectGetType(object)
if keyString == nil {
return
}
print("key \(keyString!) is present in dictionary, type \(objectType.rawValue)")
var ptrObjectValue:UnsafePointer<Int8>? = nil
switch objectType {
// ObjectType is enum of:
// Null
// Boolean
// Integer
// Real
// Name
// String
// Array
// Dictionary
// Stream
case .boolean:
// Boolean
var objectBoolean:CGPDFBoolean = 0
if CGPDFObjectGetValue(object, objectType, &objectBoolean) {
let testbool = NSNumber(value: objectBoolean)
print("Boolean value \(testbool)")
}
case .integer:
// Integer
var objectInteger:CGPDFInteger? = nil
if CGPDFObjectGetValue(object, objectType, &objectInteger) {
print("Integer value \(objectInteger)")
}
case .real:
// Real
var objectReal:CGPDFReal? = nil
if CGPDFObjectGetValue(object, objectType, &objectReal) {
print("Real value \(objectReal)")
}
case .name:
// Name
if (CGPDFObjectGetValue(object, objectType, &ptrObjectValue)) {
let stringName = String(cString: UnsafePointer<CChar>(ptrObjectValue!), encoding: String.Encoding.isoLatin1)
print("Name value: \(stringName!)")
}
case .string:
// String
_ = CGPDFObjectGetValue(object, objectType, &ptrObjectValue)
let stringValue = CGPDFStringCopyTextString(OpaquePointer(ptrObjectValue!))
print("String value: \(stringValue!)")
case .array:
// Array
print("Array")
var objectArray:CGPDFArrayRef? = nil
if (CGPDFObjectGetValue(object, objectType, &objectArray))
{
print("array: \(arrayFromPDFArray(pdfArray: objectArray!))")
}
case .dictionary:
// Dictionary
var objectDictionary:CGPDFDictionaryRef? = nil
if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) {
let count = CGPDFDictionaryGetCount(objectDictionary!)
print("Found dictionary with \(count) entries")
if !(keyString == "Parent") && !(keyString == "P") {
//catalogLevel = catalogLevel + 1
CGPDFDictionaryApplyFunction(objectDictionary!, { (key, object, info) -> Void in
printPDFKeys(key: key, object: object) // , info: info)
}, nil)
// CGPDFDictionaryApplyFunction(objectDictionary!, printPDFKeys as! CGPDFDictionaryApplierFunction, nil)
//catalogLevel = catalogLevel - 1
}
}
case .stream:
// Stream
print("Stream")
var objectStream:CGPDFStreamRef? = nil
if (CGPDFObjectGetValue(object, objectType, &objectStream)) {
let _: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream! )!
var fmt: CGPDFDataFormat = .raw
let streamData: CFData = CGPDFStreamCopyData(objectStream!, &fmt)!;
let data = NSData(data: streamData as Data)
let dataString = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue)
let dataLength: Int = CFDataGetLength(streamData)
print("data stream (length=\(dataLength)):")
if dataLength < 400 {
print(dataString)
}
}
default:
print("Null")
}
}
// convert a PDF array into an objC one
func arrayFromPDFArray(pdfArray: CGPDFArrayRef ) -> NSMutableArray {
var _:Int = 0
let tmpArray: NSMutableArray = NSMutableArray()
let count = CGPDFArrayGetCount(pdfArray)
for i in 0..<count {
var value:CGPDFObjectRef? = nil
if (CGPDFArrayGetObject(pdfArray, i, &value)) {
if let object = objectForPDFObject(object: value!) {
tmpArray.add(object)
}
}
}
return tmpArray
}
func objectForPDFObject( object: CGPDFObjectRef) -> AnyObject? {
let objectType: CGPDFObjectType = CGPDFObjectGetType(object)
var ptrObjectValue:UnsafePointer<Int8>? = nil
switch (objectType) {
case .boolean:
// Boolean
var objectBoolean = CGPDFBoolean()
if CGPDFObjectGetValue(object, objectType, &objectBoolean) {
let testbool = NSNumber(value: objectBoolean)
return testbool
}
case .integer:
// Integer
var objectInteger = CGPDFInteger()
if CGPDFObjectGetValue(object, objectType, &objectInteger) {
return objectInteger as AnyObject
}
case .real:
// Real
var objectReal = CGPDFReal()
if CGPDFObjectGetValue(object, objectType, &objectReal) {
return objectReal as AnyObject
}
case .string:
_ = CGPDFObjectGetValue(object, objectType, &ptrObjectValue)
let stringValue = CGPDFStringCopyTextString(OpaquePointer(ptrObjectValue!))
return stringValue
case .dictionary:
// Dictionary
var objectDictionary:CGPDFDictionaryRef? = nil
if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) {
let count = CGPDFDictionaryGetCount(objectDictionary!)
print("In array, found dictionary with \(count) entries")
CGPDFDictionaryApplyFunction(objectDictionary!, { (key, object, info) -> Void in
printPDFKeys(key: key, object: object) // , info: info)
}, nil)
// CGPDFDictionaryApplyFunction(objectDictionary!, printPDFKeys as! CGPDFDictionaryApplierFunction, nil)
}
case .stream:
// Stream
var objectStream:CGPDFStreamRef? = nil
if (CGPDFObjectGetValue(object, objectType, &objectStream)) {
let _: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream! )!
var fmt: CGPDFDataFormat = .raw
let streamData: CFData = CGPDFStreamCopyData(objectStream!, &fmt)!;
let data = NSData(data: streamData as Data)
let dataString = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue)
print("data stream (length=\(CFDataGetLength(streamData))):")
return dataString
}
default:
return nil
}
return nil
}
func parse () {
let filepath = ("~/Desktop/doc.pdf" as NSString).expandingTildeInPath
let urlDocument = NSURL(fileURLWithPath: filepath)
let myDocument = CGPDFDocument(urlDocument)
if myDocument != nil {
let numPages = myDocument?.numberOfPages
print("Number of pages: \(numPages)")
// Get complete catalog
let myCatalog = myDocument?.catalog
CGPDFDictionaryApplyFunction(myCatalog!, { (key, object, info) -> Void in
printPDFKeys(key: key, object: object) // , info: info)
}, nil)
// CGPDFDictionaryApplyFunction(myCatalog!, printPDFKeys, nil)
let myInfo = myDocument?.info
CGPDFDictionaryApplyFunction(myInfo!, { (key, object, info) -> Void in
printPDFKeys(key: key, object: object) // , info: info)
}, nil)
// CGPDFDictionaryApplyFunction(myInfo!, printPDFKeys, nil)
} else {
print("Cannot open PDF document")
}
}
parse()
Your parsing retrieving high level dictionary and info data is correct, but you need to expand the decoding in CGPDFDictionaryApplyFunction to display the values of PDF data according their types (integer, string, array, dictionary, and so on). The syntax of the CGPDFDictionaryApplierFunction you are calling is:
typealias CGPDFDictionaryApplierFunction = (UnsafePointer<Int8>, COpaquePointer, UnsafeMutablePointer<()>) -> Void
Your program is displaying the pointers to the data, you could access the data values according their types as below (Swift 2):
let filepath = "/Users/ben/Desktop/Test.pdf"
let urlDocument = NSURL(fileURLWithPath: filepath)
let myDocument = CGPDFDocumentCreateWithURL(urlDocument)
if myDocument != nil {
let numPages = CGPDFDocumentGetNumberOfPages(myDocument)
print("Number of pages: \(numPages)")
// Get complete catalog
let myCatalog = CGPDFDocumentGetCatalog(myDocument)
CGPDFDictionaryApplyFunction(myCatalog, printPDFKeys, nil)
let myInfo = CGPDFDocumentGetInfo(myDocument)
CGPDFDictionaryApplyFunction(myInfo, printPDFKeys, nil)
} else {
print("Cannot open PDF document")
}
In order to be called from the CGPDFDictionaryApplyFunction, the printPDFKeys is to be called as a global function (outside your main class), alternately you could insert the code in a closure of CGPDFDictionaryApplyFunction as in your example above. The below code is shortened and is not including complete protection against errors and null values.
func printPDFKeys( key: UnsafePointer<Int8>, object: COpaquePointer, info: UnsafeMutablePointer<()>) {
let contentDict: CGPDFDictionaryRef = CGPDFDictionaryRef(info)
let keyString = String(CString: UnsafePointer<CChar>(key), encoding: NSISOLatin1StringEncoding)
let objectType = CGPDFObjectGetType(object)
if keyString == nil {
return
}
print("key \(keyString!) is present in dictionary, type \(objectType.rawValue)")
var ptrObjectValue = UnsafePointer<Int8>()
switch objectType {
// ObjectType is enum of:
// Null
// Boolean
// Integer
// Real
// Name
// String
// Array
// Dictionary
// Stream
case .Boolean:
// Boolean
var objectBoolean = CGPDFBoolean()
if CGPDFObjectGetValue(object, objectType, &objectBoolean) {
let testbool = NSNumber(unsignedChar: objectBoolean)
print("Boolean value \(testbool)")
}
case .Integer:
// Integer
var objectInteger = CGPDFInteger()
if CGPDFObjectGetValue(object, objectType, &objectInteger) {
print("Integer value \(objectInteger)")
}
case .Real:
// Real
var objectReal = CGPDFReal()
if CGPDFObjectGetValue(object, objectType, &objectReal) {
print("Real value \(objectReal)")
}
case .Name:
// Name
if (CGPDFObjectGetValue(object, objectType, &ptrObjectValue)) {
let stringName = String(CString: UnsafePointer<CChar>(ptrObjectValue), encoding: NSISOLatin1StringEncoding)
print("Name value: \(stringName!)")
}
case .String:
// String
let valueFound = CGPDFObjectGetValue(object, objectType, &ptrObjectValue)
let stringValue = CGPDFStringCopyTextString(COpaquePointer(ptrObjectValue))
print("String value: \(stringValue!)")
case .Array:
// Array
print("Array")
var objectArray = CGPDFArrayRef()
if (CGPDFObjectGetValue(object, objectType, &objectArray))
{
print("array: \(arrayFromPDFArray(objectArray))")
}
case .Dictionary:
// Dictionary
var objectDictionary = CGPDFDictionaryRef()
if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) {
let count = CGPDFDictionaryGetCount(objectDictionary)
print("Found dictionary with \(count) entries")
if !(keyString == "Parent") && !(keyString == "P") {
//catalogLevel = catalogLevel + 1
CGPDFDictionaryApplyFunction(objectDictionary, printPDFKeys, nil)
//catalogLevel = catalogLevel - 1
}
}
case .Stream:
// Stream
print("Stream")
var objectStream = CGPDFStreamRef()
if (CGPDFObjectGetValue(object, objectType, &objectStream)) {
let dict: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream )
var fmt: CGPDFDataFormat = .Raw
let streamData: CFDataRef = CGPDFStreamCopyData(objectStream, &fmt)!;
let data = NSData(data: streamData)
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
let dataLength: Int = CFDataGetLength(streamData)
print("data stream (length=\(dataLength)):")
if dataLength < 400 {
print(dataString)
}
}
default:
print("Null")
}
}
// convert a PDF array into an objC one
func arrayFromPDFArray(pdfArray: CGPDFArrayRef ) -> NSMutableArray {
var i:Int = 0
var tmpArray: NSMutableArray = NSMutableArray()
let count = CGPDFArrayGetCount(pdfArray)
for i in 0..<count {
var value = CGPDFObjectRef()
if (CGPDFArrayGetObject(pdfArray, i, &value)) {
if let object = objectForPDFObject(value) {
tmpArray.addObject(object)
}
}
}
return tmpArray
}
func objectForPDFObject( object: CGPDFObjectRef) -> AnyObject? {
let objectType: CGPDFObjectType = CGPDFObjectGetType(object)
var ptrObjectValue = UnsafePointer<Int8>()
switch (objectType) {
case .Boolean:
// Boolean
var objectBoolean = CGPDFBoolean()
if CGPDFObjectGetValue(object, objectType, &objectBoolean) {
let testbool = NSNumber(unsignedChar: objectBoolean)
return testbool
}
case .Integer:
// Integer
var objectInteger = CGPDFInteger()
if CGPDFObjectGetValue(object, objectType, &objectInteger) {
return objectInteger
}
case .Real:
// Real
var objectReal = CGPDFReal()
if CGPDFObjectGetValue(object, objectType, &objectReal) {
return objectReal
}
case .String:
let valueFound = CGPDFObjectGetValue(object, objectType, &ptrObjectValue)
let stringValue = CGPDFStringCopyTextString(COpaquePointer(ptrObjectValue))
return stringValue
case .Dictionary:
// Dictionary
var objectDictionary = CGPDFDictionaryRef()
if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) {
let count = CGPDFDictionaryGetCount(objectDictionary)
print("In array, found dictionary with \(count) entries")
CGPDFDictionaryApplyFunction(objectDictionary, printPDFKeys, nil)
}
case .Stream:
// Stream
var objectStream = CGPDFStreamRef()
if (CGPDFObjectGetValue(object, objectType, &objectStream)) {
let dict: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream )
var fmt: CGPDFDataFormat = .Raw
let streamData: CFDataRef = CGPDFStreamCopyData(objectStream, &fmt)!;
let data = NSData(data: streamData)
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
print("data stream (length=\(CFDataGetLength(streamData))):")
return dataString
}
default:
return nil
}
return nil
}
Made a parser (based on previous answers) that crawls the PDF hierarchy and gives you a JSON.
// Parse PDF into JSON.
PDFParser.parse(pdfUrl: pdfFileURL, into: jsonFileURL)
// Parse PDF into Dictionary.
let pdf: [String:Any?] = PDFParser.parse(pdfUrl: pdfFileURL)
Gives you:
{
"Catalog" : {
"Pages<Dictionary>" : {
"MediaBox<Array>" : [
0,
0,
612,
792
],
"Type<Name>" : "Pages",
"Kids<Array>" : [
{
"Rotate<Integer>" : 0,
"MediaBox<Array>" : [
0,
0,
595.27499999999998,
841.88999999999999
],
"Parent<Dictionary>" : "<PARENT_NOT_SERIALIZED>",
"Resources<Dictionary>" : {
"ColorSpace<Dictionary>" : {
"Cs1<Array>" : [
"ICCBased",
{
"N<Integer>" : 3,
"Filter<Name>" : "FlateDecode",
"Alternate<Name>" : "DeviceRGB",
"Length<Integer>" : 2612
}
]
}
...
To get from CGPDFDocument (like original question):
// Get document catalog.
guard
let document = CGPDFDocument(pdfFileURL as CFURL),
let catalog = document.catalog
else { return }
// Parse into dictionary.
let catalogDictionary = PDFParser.value(from: catalog)
Gives you a pretty usual Swift dictionary. Console output:
Optional(["Pages<Dictionary>": Optional({
"Count<Integer>" = 1;
"Kids<Array>" = (
{
"ArtBox<Array>" = (
"28.3465",
"325.193",
"393.389",
"813.543"
);
"Contents<Stream>" = {
Data = "q Q q 0 0 595.276 841.89 re W n 1 0 1 0 k /Gs1 gs 201.8862 420.9449 m 201.8862\n473.8269 244.7562 516.6959 297.6372 516.6959 c 350.5192 516.6959 393.3892\n473.8269 393.3892 420.9449 c 393.3892 368.0629 350.5192 325.1939 297.6372\n325.1939 c 244.7562 325.1939 201.8862 368.0629 201.8862 420.9449 c f Q q 28.346 530.078 283.464 283.465\nre W n 0 0 0 1 k /Gs1 gs BT 12 0 0 12 28.3467 803.499 Tm /Tc1 1 Tf [ (h) 4\n(ttp://epp) 7 (z.eu) ] TJ ET Q";
"Filter<Name>" = FlateDecode;
"Length<Integer>" = 237;
};
"MediaBox<Array>" = (
0,
0,
"595.2760000000001",
"841.89"
);
"Parent<Dictionary>" = "<PARENT_NOT_SERIALIZED>";
"Resources<Dictionary>" = {
"ExtGState<Dictionary>" = {
"Gs1<Dictionary>" = {
"OPM<Integer>" = 1;
"Type<Name>" = ExtGState;
};
};
...
ParsePDF.swift:
//
// PDFParser.swift
// PDFParser
//
// Copyright (c) 2020 Geri Borbás http://www.twitter.com/_eppz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import PDFKit
class PDFParser
{
/// Shorthand for type strings.
static let namesForTypes: [CGPDFObjectType:String] =
[
.null : "Null",
.boolean : "Boolean",
.integer : "Integer",
.real : "Real",
.name : "Name",
.string : "String",
.array : "Array",
.dictionary : "Dictionary",
.stream : "Stream",
CGPDFObjectTypeObject : "Object",
]
struct Message
{
static let parentNotSerialized = "<PARENT_NOT_SERIALIZED>"
static let couldNotParseValue = "<COULD_NOT_PARSE_VALUE>"
static let couldNotGetStreamData = "<COULD_NOT_GET_STREAM_DATA>"
static let unknownStreamDataFormat = "<UNKNOWN_STREAM_DATA_FORMAT>"
}
/// Parse a PDF file into a JSON file.
static func parse(pdfUrl: URL, into jsonURL: URL)
{
do
{
let pdf = PDFParser.parse(pdfUrl: pdfUrl)
let data = try JSONSerialization.data(withJSONObject: pdf, options: .prettyPrinted)
try data.write(to: jsonURL, options: [])
}
catch
{ print(error) }
}
/// Parse a PDF file into a JSON file.
static func parse(pdfUrl: URL) -> [String:Any?]
{
// Document.
guard
let document = CGPDFDocument(pdfUrl as CFURL),
let catalog = document.catalog,
let info = document.info
else
{
print("Cannot open PDF.")
return [:]
}
// Parse.
return [
"Catalog" : PDFParser.value(from: catalog),
"Info" : PDFParser.value(from: info)
]
}
static func value(from object: CGPDFObjectRef) -> Any?
{
switch (CGPDFObjectGetType(object))
{
case .null:
return nil
case .boolean:
var valueRef: CGPDFBoolean = 0
if CGPDFObjectGetValue(object, .boolean, &valueRef)
{ return Bool(valueRef == 0x01) }
case .integer:
var valueRef: CGPDFInteger = 0
if CGPDFObjectGetValue(object, .integer, &valueRef)
{ return valueRef as Int }
case .real:
var valueRef: CGPDFReal = 0.0
if CGPDFObjectGetValue(object, .real, &valueRef)
{ return Double(valueRef) }
case .name:
var objectRefOrNil: UnsafePointer<Int8>? = nil
if
CGPDFObjectGetValue(object, .name, &objectRefOrNil),
let objectRef = objectRefOrNil,
let string = String(cString: objectRef, encoding: String.Encoding.isoLatin1)
{ return string }
case .string:
var objectRefOrNil: UnsafePointer<Int8>? = nil
if
CGPDFObjectGetValue(object, .string, &objectRefOrNil),
let objectRef = objectRefOrNil,
let stringRef = CGPDFStringCopyTextString(OpaquePointer(objectRef))
{ return stringRef as String }
case .array:
var arrayRefOrNil: CGPDFArrayRef? = nil
if
CGPDFObjectGetValue(object, .array, &arrayRefOrNil),
let arrayRef = arrayRefOrNil
{
var array: [Any] = []
for index in 0 ..< CGPDFArrayGetCount(arrayRef)
{
var eachObjectRef: CGPDFObjectRef? = nil
if
CGPDFArrayGetObject(arrayRef, index, &eachObjectRef),
let eachObject = eachObjectRef,
let eachValue = PDFParser.value(from: eachObject)
{ array.append(eachValue) }
}
return array
}
case .stream:
var streamRefOrNil: CGPDFStreamRef? = nil
if
CGPDFObjectGetValue(object, .stream, &streamRefOrNil),
let streamRef = streamRefOrNil,
let streamDictionaryRef = CGPDFStreamGetDictionary(streamRef)
{
// Get stream dictionary.
var streamNSMutableDictionary = NSMutableDictionary()
Self.collectObjects(from: streamDictionaryRef, into: &streamNSMutableDictionary)
var streamDictionary = streamNSMutableDictionary as! [String: Any?]
// Get data.
var dataString: String? = Message.couldNotGetStreamData
var streamDataFormat: CGPDFDataFormat = .raw
if let streamData: CFData = CGPDFStreamCopyData(streamRef, &streamDataFormat)
{
switch streamDataFormat
{
case .raw: dataString = String(data: NSData(data: streamData as Data) as Data, encoding: String.Encoding.utf8)
case .jpegEncoded, .JPEG2000: dataString = NSData(data: streamData as Data).base64EncodedString()
#unknown default: dataString = Message.unknownStreamDataFormat
}
}
// Add to dictionary.
streamDictionary["Data"] = dataString
return streamDictionary
}
case .dictionary:
var dictionaryRefOrNil: CGPDFDictionaryRef? = nil
if
CGPDFObjectGetValue(object, .dictionary, &dictionaryRefOrNil),
let dictionaryRef = dictionaryRefOrNil
{
var dictionary = NSMutableDictionary()
Self.collectObjects(from: dictionaryRef, into: &dictionary)
return dictionary as! [String: Any?]
}
#unknown default:
var dictionary = NSMutableDictionary()
Self.collectObjects(from: object, into: &dictionary)
return dictionary as! [String: Any?]
}
// No known case.
return nil
}
static func collectObjects(from dictionaryRef: CGPDFDictionaryRef, into dictionaryPointer: UnsafeMutableRawPointer?)
{
CGPDFDictionaryApplyFunction(
dictionaryRef,
{
(eachKeyPointer, eachObject, eachContextOrNil: UnsafeMutableRawPointer?) -> Void in
// Unwrap dictionary.
guard let dictionary = eachContextOrNil?.assumingMemoryBound(to: NSMutableDictionary.self).pointee
else { return print("Could not unwrap dictionary.") }
// Unwrap key.
guard let eachKey = String(cString: UnsafePointer<CChar>(eachKeyPointer), encoding: .isoLatin1)
else { return print("Could not unwrap key.") }
// Type.
guard let eachTypeName = PDFParser.namesForTypes[CGPDFObjectGetType(eachObject)]
else { return print("Could not unwrap type.") }
// Assemble.
let eachDictionaryKey = "\(eachKey)<\(eachTypeName)>" as NSString
// Skip parent.
guard eachKey != "Parent"
else
{
dictionary.setObject(Message.parentNotSerialized, forKey: eachDictionaryKey)
return
}
// Parse value.
guard let eachValue = PDFParser.value(from: eachObject)
else
{
dictionary.setObject(Message.couldNotParseValue, forKey: eachDictionaryKey)
fatalError("😭")
// return
}
// Set.
dictionary.setObject(eachValue, forKey: eachDictionaryKey)
},
dictionaryPointer
)
}
}