Using OpenType font features in iOS - ios

It seems that in iOS, OpenType font-features and their selectors are identified by numbers. For example we have these in Frameworks/CoreText/SFNTLayoutTypes.h:
enum {
kAllTypographicFeaturesType = 0,
kLigaturesType = 1,
...
kLanguageTagType = 39,
kCJKRomanSpacingType = 103,
kLastFeatureType = -1
}
/*
* Summary:
* Selectors for feature type kAllTypographicFeaturesType
*/
enum {
kAllTypeFeaturesOnSelector = 0,
kAllTypeFeaturesOffSelector = 1
};
...
We can list font-features using this:
extension UIFont {
typealias FontFeatureInfo = [String: Any]
func listFeatures() {
guard let fontFeatures = CTFontCopyFeatures(self) as? [FontFeatureInfo] else {
debugPrint("Could not copy font features.")
return
}
fontFeatures.forEach { fontFeatureInfo in
print(fontFeatureInfo)
}
}
}
and it prints something like this:
[
CTFeatureTypeName: All Typographic Features,
CTFeatureTypeIdentifier: 0,
CTFeatureTypeSelectors: <__NSArrayM 0x6000000e15c0>({
CTFeatureSelectorDefault = 1;
CTFeatureSelectorIdentifier = 0;
CTFeatureSelectorName = On;
})
]
[
CTFeatureTypeName: Ligatures,
CTFeatureTypeIdentifier: 1,
CTFeatureTypeSelectors: <__NSArrayM 0x6000000e1e90>({
CTFeatureSelectorIdentifier = 4;
CTFeatureSelectorName = "Rare Ligatures";
})
]
...
In web development we use these features by their string-based IDs, like ss01, dlig, salt, dnom and etc.
font-feature-settings: "ss01";
If we run code above for Inter typeface, there are many font-features that have CTFeatureOpenTypeTag: _opentype-tag_, but does not have any CTFeatureTypeIdentifier.
For example it shows:
[
CTFeatureOpenTypeTag: numr,
CTFeatureTypeSelectors: <__NSArrayM 0x6000000d8e40>({
CTFeatureOpenTypeValue = 0;
CTFeatureSelectorDefault = 1;
CTFeatureSelectorName = Off;
}, {
CTFeatureOpenTypeValue = 1;
CTFeatureSelectorName = On;
}),
CTFeatureTypeExclusive: 1,
CTFeatureTypeName: Numerators
]
[
CTFeatureOpenTypeTag: salt,
CTFeatureTypeName: Stylistic Alternates,
CTFeatureTypeSelectors: <__NSArrayM 0x6000000d9380>({
CTFeatureOpenTypeValue = 0;
CTFeatureSelectorDefault = 1;
CTFeatureSelectorName = Off;
}, {
CTFeatureOpenTypeValue = 1;
CTFeatureSelectorName = On;
}),
CTFeatureTypeExclusive: 1
]
The question is how can I use these features without numeric identifiers? How can I use combination of CTFeatureOpenTypeTag and CTFeatureOpenTypeValue to enable OpenType font-features?

Related

How to use UIFontDescriptor when CTFeatureTypeIdentifier is not available

I use the following snippet to get the font features:
let font = UIFont.systemFont(ofSize: 16)
let features: NSArray = CTFontCopyFeatures(font)!
print("properties = \(features)")
So If I see this:
{
CTFeatureTypeExclusive = 1;
CTFeatureTypeIdentifier = 22;
CTFeatureTypeName = "Text Spacing";
CTFeatureTypeSelectors = (
{
CTFeatureSelectorDefault = 1;
CTFeatureSelectorIdentifier = 7;
CTFeatureSelectorName = "No Change";
},
{
CTFeatureSelectorIdentifier = 8;
CTFeatureSelectorName = "No Kerning";
}
);
I can translate that into Swift as:
let fontDescriptorFeatureSettings = [
[ UIFontDescriptor.FeatureKey.featureIdentifier : 22,
UIFontDescriptor.FeatureKey.typeIdentifier : 8], ]
This successfully disables kerning for me.
In the same snippet, I see:
{
CTFeatureOpenTypeTag = cv05;
CTFeatureSampleText = I;
CTFeatureTooltipText = "\U00a9 2015-2022 Apple Inc. All rights reserved.";
CTFeatureTypeExclusive = 1;
CTFeatureTypeName = "Seriffed Capital I";
CTFeatureTypeSelectors = (
{
CTFeatureOpenTypeValue = 0;
CTFeatureSelectorDefault = 1;
CTFeatureSelectorName = Off;
},
{
CTFeatureOpenTypeValue = 1;
CTFeatureSelectorName = On;
}
);
How do I translate that into Swift to get a Seriffed 'I'?
The issue for me is the absence of:
CTFeatureTypeIdentifier
First of all, your kerning disablement code is wrong, in two ways:
In the very confusing UIFontDescriptor.FeatureKey namespace, .featureIdentifier and .typeIdentifier are deprecated, replaced respectively by .type and .selector. (I filed a bug on those old names years ago, and it looks like Apple heard me and changed them. Hooray!)
For the values corresponding to those keys, don't use raw numbers if you don't have to; that's illegible and fragile. These things often have constant names.
So when you say
let fontDescriptorFeatureSettings = [
[
UIFontDescriptor.FeatureKey.featureIdentifier: 22,
UIFontDescriptor.FeatureKey.typeIdentifier: 8,
],
]
You should be saying
let fontDescriptorFeatureSettings = [
[
UIFontDescriptor.FeatureKey.type: kTextSpacingType,
UIFontDescriptor.FeatureKey.selector: 8,
],
]
// I couldn't find a name for the `8` here
Okay, so. To get a seriffed I, specify the kStylisticAltSixOnSelector as your kStylisticAlternativesType.
let fontDescriptorFeatureSettings = [
[
UIFontDescriptor.FeatureKey.type: kStylisticAlternativesType,
UIFontDescriptor.FeatureKey.selector: kStylisticAltSixOnSelector,
],
]
Now, you might say, "Okay, fine, but I'm also getting some other changes I didn't necessarily want, such as the curvy ell and the slashed zero."
That is true, but at the high level at which you are operating with UIFontDescriptor, there's nothing you can do about that. What you have found in the dictionary are individual glyph variants. The only way you can access those is by drawing your text directly with CoreText, and I don't think there's any chance you're going to want to get down to that level of text drawing. What you want to do is just pop this text into a UILabel or similar, and be done with it; in that case, turning on the alt six variant is the best you can do.

Swift: Unwrapping a dictionary messes up the order of the values inside of it

I am receiving JSON which then I'm storing in a dictionary variable menus. However, the problem is after unwrapping the data, the order of the "menus" gets messed up. And so they do not get displayed in order in the UI.
print("INSIDE CMSessionManger.login result[menus]: ", result["menus"]!)
/*
INSIDE CMSessionManger.login result[menus]: {
1 = {
id = 1;
label = Accueil;
maxNumberPerPage = 20;
};
2 = {
id = 2;
label = "Mes favoris";
maxNumberPerPage = 20;
};
3 = {
id = 3;
label = "Les s\U00e9lections";
maxNumberPerPage = 20;
};
4 = {
id = 4;
label = "Acc\U00e8s illimit\U00e9";
maxNumberPerPage = 20;
};
}
**/
print("INSIDE CMSessionManger.login type(of: result[menus]!: ", type(of: result["menus"]!))
if let menus = result["menus"] as? [String: Any]{
print("INSIDE CMSessionManger.login Setting menus")
print("INSIDE CMSessionManger.login menus: ", menus)
/*
INSIDE CMSessionManger.login menus: ["2": {
id = 2;
label = "Mes favoris";
maxNumberPerPage = 20;
}, "4": {
id = 4;
label = "Acc\U00e8s illimit\U00e9";
maxNumberPerPage = 20;
}, "1": {
id = 1;
label = Accueil;
maxNumberPerPage = 20;
}, "3": {
id = 3;
label = "Les s\U00e9lections";
maxNumberPerPage = 20;
}]
**/
}
I really don't understand why would a simple unwrap change the order of the dictionary values.
Dictionaries are unordered collections. There is no way to guarantee that you will get the items back in the same order in which you add them. It looks like your keys are numbers, and are repeated in the "id" key/value pair of each dictionary entry. If you really want the data to have a specific order, why not send the items in an array?
You could use the Dictinary.values property to get the entries in your dictionary into an array, and then sort those entries by their id values.
Here is sample code that does that:
let aDict:[Int: [String:Any]] = [
1 :
["id": 1,
"label": "Accueil",
"maxNumberPerPage":20 ],
2 : ["id": 2,
"label": "Mes favoris",
"maxNumberPerPage": 20
],
3 : ["id": 3,
"label": "Les sélections",
"maxNumberPerPage": 20
]
]
let values = aDict.values.sorted{
($0["id"] as! Int) < ($1["id"] as! Int) }
values.forEach {print($0)}
Note that the code above will crash if the contents of any of the inner dictionaries does not contain a key/value pair with the key "id" and with a value that is an Int. Production code that parsed JSON would need input validation.

Read out specific JSON from NSArray

This is the JSON object I'm getting from the openweathermap - API:
["main": {
humidity = 12;
pressure = 922;
temp = "271.13";
"temp_max" = "171.15";
"temp_min" = "291.15";
}, "name": mycity, "id": 299129219, "coord": {
lat = "92.1211";
lon = "182.1211";
}, "weather": <__NSArrayI 0x1c042e820>(
{
description = "light snow";
icon = 13n;
id = 120;
main = Snow;
},
{
description = mist;
icon = 50n;
id = 722;
main = Mist;
}
)
, "clouds": {
all = 12;
}, "dt": 211, "base": stations, "sys": {
country = XXX;
id = 4891;
message = "0.02221";
sunrise = 1221122112;
sunset = 4343344343;
type = 1;
}, "cod": 100, "visibility": 3200, "wind": {
speed = 3;
}]
Because I like to readout some information (like the current temperature, the weather description, etc.) I tried to use this few lines:
let temperature = (result["main"] as! [String:Double])["temp"]!
The code above is working fine but I got massive problems reading out the description of the first Weather element (called "light snow"):
let description = (result["weather"] as! [String:Any]).first["description"]! //(result should be : "light snow")
... doesn't seems working at all.
So how can I fix this issue?
Thanks a million in advance.
Also used this API :)
This worked for me:
guard let weathersArray = json["weather"] as? [[String: Any]],
let weatherJson = weathersArray.first,
let description = weatherJson["description"] as? String
else { return }
Update: in case you want all the array elements just loop over the weathersArray and get all the descriptions.

Append multiple NSMutableArray data to another NSMutableArray in swift 3

Let's say, there is shipping nsdictionary value is required to send to server, and there is items NSMutableArray inside shipping and, again there is sub-items NSMutableArray inside items.
That's why I've coded like that:
for cake in cakes {
for item in cakeItemsOpt {
let optionalItem = [
"item_req_mod_p_id": item.cakeItemModId,
"l_req_mod_p_id": item.cakeModId,
"mod_id": item.cakeModId,
"mod_qty": item.cakeQty
]
itemsOptional.add(optionalItem)
}
for item in cakeItemsReq {
let requiredItem = [
"item_req_mod_p_id": item.cakeItemModId,
"l_req_mod_p_id": item.cakeModId,
"mod_id": item.cakeModId,
"mod_qty": "1"
]
itemsRequired.add(requiredItem)
}
let cakeParam: NSDictionary = [
"id": cake.cakeId,
"qty": cake.cakeQty,
"type": cake.cakeType,
"name": cake.cakeName,
"image": cake.cakeImage,
"req_modifiers": itemsRequired,
"opt_modifiers": itemsOptional
]
items.add(cakeParam)
}
let param: NSDictionary = [
"user_id": user_id,
"token": token,
"delivery": "del",
"delivery_remark": delivery_remark,
"delivery_date": delivery_date,
"delivery_time": delivery_time,
"delivery_charge": delivery_charge,
"item": items,
"payment_method_id": payment_id,
] as [String : Any]
param is main object to send to server.
But the result is not what I've expected,
["delivery_remark": "heavy", "payment_method_id": "4", "delivery": "del", "token": "0l8BwedwkZQltcFYFhCpicQZeQOhikyYLxa8R3u2", "item": <__NSArrayM 0x60800044ea90>(
{
id = 11;
image = "";
name = "Red Velvet 16cm";
"opt_modifiers" = (
{
"item_req_mod_p_id" = 49;
"l_req_mod_p_id" = 3;
"mod_id" = 3;
"mod_qty" = "";
},
{
"item_req_mod_p_id" = 49;
"l_req_mod_p_id" = 4;
"mod_id" = 4;
"mod_qty" = "";
}
);
qty = 2;
"req_modifiers" = (
{
"item_req_mod_p_id" = 55;
"l_req_mod_p_id" = 8;
"mod_id" = 8;
"mod_qty" = 1;
},
{
"item_req_mod_p_id" = 56;
"l_req_mod_p_id" = 3;
"mod_id" = 3;
"mod_qty" = 1;
}
);
"run_no" = "6260-1c86";
"small_candles" = "";
"sponge_color_id" = "";
"theme_id" = "";
"theme_name" = "";
type = item;
"word_color_id" = "";
}
)
, "delivery_charge": "15.00", "user_id": "21", "delivery_time": "3", "delivery_date": "2017-02-27"]
There is <__NSArrayM 0x60800044ea90> included in my object. Please let me know how to correct my coding to get correct result. Thanks.
Then i guess you want something like
let data: [String : [String : [String : [String]]]]
data = [
"shipping" : ["items" : ["subitemKey" : ["subitemArray"]]]
]
So you have a dictionary that contains a dictionary for a key "shipping" that contains a dictionary for key "items" that contains arrays for keys of different subitems

iOS Swift - Google Place API (Web) returns JSON in a strange format

I'm building an iOS app in Swift 3 and I'm using Google Places API to get places data. I'm using the iOS API to get most of the data I need. But I also need to get the opening times which aren't (yet) provided by the iOS API.
So I have to query the web API to get that information. The request side of things is working fine, however, once I get the result and I turn it into JSON with JSONSerialisationand then print the output in the console, the format of this JSON is strange and I'm struggling to parse it.
When I paste that into a JSON reader it says it's invalid.
Here is the JSON that looks strange to me with these "(" and "=":
["html_attributions": <__NSArray0 0x17001ccf0>(
)
, "result": {
"address_components" = (
{
"long_name" = 611;
"short_name" = 611;
types = (
"street_number"
);
},
{
"long_name" = "Post Street";
"short_name" = "Post St";
types = (
route
);
},
{
"long_name" = "Lower Nob Hill";
"short_name" = "Lower Nob Hill";
types = (
neighborhood,
political
);
},
{
"long_name" = "San Francisco";
"short_name" = SF;
types = (
locality,
political
);
},
{
"long_name" = "San Francisco County";
"short_name" = "San Francisco County";
types = (
"administrative_area_level_2",
political
);
},
{
"long_name" = California;
"short_name" = CA;
types = (
"administrative_area_level_1",
political
);
},
{
"long_name" = "United States";
"short_name" = US;
types = (
country,
political
);
},
{
"long_name" = 94109;
"short_name" = 94109;
types = (
"postal_code"
);
}
);
"adr_address" = "<span class=\"street-address\">611 Post St</span>, <span class=\"locality\">San Francisco</span>, <span class=\"region\">CA</span> <span class=\"postal-code\">94109</span>, <span class=\"country-name\">USA</span>";
"formatted_address" = "611 Post St, San Francisco, CA 94109, USA";
"formatted_phone_number" = "(415) 817-1391";
geometry = {
location = {
lat = "37.7877411";
lng = "-122.4120067";
};
viewport = {
northeast = {
lat = "37.78791149999999";
lng = "-122.4119977";
};
southwest = {
lat = "37.7876843";
lng = "-122.4120337";
};
};
};
icon = "https://maps.gstatic.com/mapfiles/place_api/icons/cafe-71.png";
id = 6a6d8b640d5260886eac5ce1b3aa73b3b0d0bb1c;
"international_phone_number" = "+1 415-817-1391";
name = "Joy's Place";
"opening_hours" = {
"open_now" = 1;
periods = (
{
close = {
day = 1;
time = 0000;
};
open = {
day = 0;
time = 0800;
};
},
{
close = {
day = 2;
time = 0000;
};
open = {
day = 1;
time = 0700;
};
},
{
close = {
day = 3;
time = 0000;
};
open = {
day = 2;
time = 0700;
};
},
{
close = {
day = 4;
time = 0000;
};
open = {
day = 3;
time = 0700;
};
},
{
close = {
day = 5;
time = 0000;
};
open = {
day = 4;
time = 0700;
};
},
{
close = {
day = 6;
time = 0000;
};
open = {
day = 5;
time = 0700;
};
},
{
close = {
day = 0;
time = 0000;
};
open = {
day = 6;
time = 0800;
};
}
);
"weekday_text" = (
"Monday: 7:00 am \U2013 12:00 am",
"Tuesday: 7:00 am \U2013 12:00 am",
"Wednesday: 7:00 am \U2013 12:00 am",
"Thursday: 7:00 am \U2013 12:00 am",
"Friday: 7:00 am \U2013 12:00 am",
"Saturday: 8:00 am \U2013 12:00 am",
"Sunday: 8:00 am \U2013 12:00 am"
);
};
photos = (
{
height = 3024;
"html_attributions" = (
"Kathleen S"
);
"photo_reference" = "CoQBdwAAAFE8a_M5quVcX6frenilHEFVXTycrrhj5R3MoS5sd_vKG1Gj46aJVAvVl_a4HVcPS4G5Z2JS00Pugdegn4is9KSfwIw6IoMNBL1zo3hK2dQjfArUezH9wfSYpNJCNzi4bUO6EesC4bT74B1-hzM6Us4gzKpLLAfImaF3VTeRX-SGEhCLAqUUWZsgueYvTkx8mEi8GhRCDxXbJR-ddaQFwJizvZIKa4EYKA";
width = 4032;
},
{
height = 3024;
"html_attributions" = (
"Jake Lim"
);
"photo_reference" = "CoQBdwAAANPkBrIvYxn3UrYliLvLgk3Z4tOmS_twOv9nbMSTFmAR00Mff8XCMWQRUeigOYw1dSheyerBgimteHAHdRMzTCAWkJ7V2yB0eBS5HBewcw2yKmGHrCuCxZDdBlovGzJrwhfo5cAUB0lFDIfAXfDGnNWifD8OmLdfEzsZt97peyIrEhC84xqRQnlK32XjZkXE5huQGhQiPI0EjQ284RkMqiYxwnoHVpXOMg";
width = 4032;
},
{
height = 2048;
"html_attributions" = (
"Hope Lynch"
);
"photo_reference" = "CoQBdwAAAFMi1hIXlNKawhA7RPl9Oah4OS6ZZyNnBRpgkHtT5gCC0brwkl55azVyulgOLsYeBkz7lpDgrpW4_9A2vg6Dtcg1bf3vVYVb1PbvADXJAV8yNwmRu9xvfSaPlgl3ty7WKW2tl8Znv8pnTMtOxfYBiTqLuFpLiEdt03pmqQMtrTKSEhADBNOSal9jDonQAMac7ghIGhTkHYMlxquEKYBuKNlbJxYY2sMmxQ";
width = 1536;
},
{
height = 3024;
"html_attributions" = (
"\Uc784\Uc77c\Uc8fc"
);
"photo_reference" = "CoQBdwAAAClp7S-w50auLq1Rtet0Glz5UjwWmSFGWKsceURE2ydHve0hFYZqf-OewYNm7xqRBA_Vdtl-nNSIgHawN5JCfyXW48fMRFi7L1mYiycxPjpZSwcH8VlQUisS1dEURkbSaY7lBH1niz197x0hhK0eTIdaPjsXboXcij7xQm6bsYSYEhDt416My2DDwuNUg7MWDrzDGhTIxwfK1eBLCjzto2X_10Ed9pvsyQ";
width = 4032;
},
{
height = 5312;
"html_attributions" = (
"Tim Lee"
);
"photo_reference" = "CoQBdwAAANNU12JQOxIa6Y4MBHzNedo42kZmStvSbw_JF-lhdCOtj7QoiuwfWvj1MjECq29AO7CM3QFeJiQQn3cV3GWilWHoI0EDXBodlGRe3MAG6tNhBnKBYB_jNuM_muu2o1ngWmsOXP6cmcuZmxK1z5w5cDX_aUrpli4vkKRkNuqIdbc_EhACCVmjsS6GxgLanF9s9KrVGhSEL_DpaNArYwocJzkX688WFLsEUQ";
width = 2988;
},
{
height = 3024;
"html_attributions" = (
"Kathleen S"
);
"photo_reference" = "CoQBdwAAAK8OjkKwRH84A_OKMhCXVemX8vxLS8nCp7c30JOvt7SiqGL5_XQCKz03ZiH-Tq2sshn_UBKttz3oyDJIhfAT2VTuDZgnbAPCSlNQUoP80yeATWPJJFcjJ4XxsKcQHF6Y5IDFzszEIAJYhMBQJ9I6RKMaBGMlRftIZeAEkFhN5AEtEhAozH6UEf-m0XthHPES1ldPGhTB3-JgPzRJ8aDX-B5_SyW2vs4E8Q";
width = 4032;
},
{
height = 3024;
"html_attributions" = (
"Deby Lepage"
);
"photo_reference" = "CoQBdwAAAJyut1B1yUQD6mq1FD8gQVNKo16sS6-xbnGeSs8jDtb3C5oF_oUcoUGpLTnQpHqxHfsFh7oL7cBud_h-h0xc2fhNuIkMBFQEWeZhCrmoqph5U79sH98jtOKJgjxs6i0W3-cyDlyURIux8oBfB3QzNhLqeHapJ_WFAMai74Z5ufo1EhDlCT1tkYirOAlgg18i1MVEGhQ_StyBaN3neLaiKNN2s-cTFEPz1w";
width = 4032;
},
{
height = 3024;
"html_attributions" = (
"Deby Lepage"
);
"photo_reference" = "CoQBdwAAAJ-OEm5p7FZZFsPIUBBlB-v5uPhGz3K_j9qD4fIF7lUTFUW3I_-O9gy_MZCUkD5XlZJXa2F9kfZ4Ru-Eb989IYc6YQzc3i_N3ozUOyuPhL2FDUqqkl-pltfNJJBoSOlDp4M4Ay9yrKQP6BpUP8i9P9L0gDgv-ycKDIWNh-vUDmtwEhA6F3RmxZ_tsbE131S_RcYoGhTOQTkUt1Zm9b8qUD7yxiWTjemxxQ";
width = 4032;
},
{
height = 3024;
"html_attributions" = (
"Kathleen S"
);
"photo_reference" = "CoQBdwAAANkAGV7dv_jVNv_mGmJ9ilcPKJ9Vx24Fs1MEwy0Ce6B6hoGZ-8I0pbFVjS99TicGo3zGdi6eUbpEm76C5XplnnkuM4HGWLN1PkzHk98vVeY4hJRGY1o2qnoLaf6DnvPBmhpbrucCJK9WCNRH3-q1UZ6lzFwgxEwFAxbf-KSwJRqkEhD2Si7TWKZNQcb7bXy29QaVGhR7Yk3dOMasHvsoNwa9y-FMxzNV4w";
width = 4032;
},
{
height = 3024;
"html_attributions" = (
"Deby Lepage"
);
"photo_reference" = "CoQBdwAAABqKoM9Is_KWHUlzFyDIAOh41GwUaOqPC5EwWqB_uwDTRsB4XbOksl4qylHlSbTuXpRrUUaAETi-I0coY8KyLtS5E6NxcM4f93iMl6kgsb8apJRGziedKV99EUNcdeaTWmWJOuxirP_7wwiadwUiwbmfjZ05e2EkAtfmjCb8vHTYEhDsAfUeyhcyQu_riR-Vrd9JGhRwlTegpWWdYn9uVGVhs02SjIyXsQ";
width = 4032;
}
);
"place_id" = "ChIJheCrEY6AhYARxCkT8_KjDWE";
rating = "4.4";
reference = "CmRRAAAAeYm_xV5gsn_eVlrvpkEolaqaHFO0CWS5rWXJ5z5QWX8ReJKLPl6eDnvFM0PD2BNMkgfui9Z42_jmUxCIeTeZi3OEaod73XuU_rzLpRAdlOm8IcTaLX6YeYJeS4qFqZxLEhBUzZmWo-SU9WY4YG8WP8GOGhSwAPbhHWRuZh0X6wC1LMD3Los44A";
reviews = (
{
aspects = (
{
rating = 2;
type = overall;
}
);
"author_name" = "Lanxin Chu";
"author_url" = "https://www.google.com/maps/contrib/117750042826931398790/reviews";
language = en;
rating = 4;
"relative_time_description" = "5 months ago";
text = "Red bean shaved ice was good and definitely did not skimp out on ingredients! Green tea latte was solid. Ambiance was pretty nice. There were a lot of people studying there; as noted by others this makes for somewhat limited seating sometimes so keep that in mind.";
time = 1470610849;
},
{
aspects = (
{
rating = 3;
type = overall;
}
);
"author_name" = "Esther Gibbs";
"author_url" = "https://www.google.com/maps/contrib/110692487628239988503/reviews";
language = en;
rating = 5;
"relative_time_description" = "a month ago";
text = "Delicious food and drink- especially their sweet potato latte! Fairtrade and organic everything and a lovely working environment";
time = 1479462640;
},
{
aspects = (
{
rating = 2;
type = overall;
}
);
"author_name" = "Johnny Wang";
"author_url" = "https://www.google.com/maps/contrib/103379264202553362035/reviews";
language = en;
"profile_photo_url" = "//lh6.googleusercontent.com/-EtpIyKVUteI/AAAAAAAAAAI/AAAAAAAAA4c/riPrz4_6j-s/photo.jpg";
rating = 4;
"relative_time_description" = "7 months ago";
text = "It's been called to my attention that I never reviewed this place, despite the number of times I've visited, which is... surprising. Honestly though, there's not too much to say: I've only ever come here for the patbingsoo, which is massively generous in portion size, and fantastically delicious in flavor, and definitely ideal for sharing. But hey, everything can be a personal serving if you try hard enough.\n\nAs for the place itself, it's a nice, chill little cafe that's been packed every single time I go, so seating can be a little hard to find at times. You'll usually find it occupied by folks busy working away on their laptops.\n\n***Accessibility Info***\n\nVenue - On the smaller side, as far as cafes/coffee shops go, with about 2/3 of the seating being accessible: those along the walls were bench seating, and there's lower coffee table seating towards the right as well, leaving only the middle aisle and outer seats along the sides accessible.\n\nBathroom - Didn't try them.";
time = 1465162538;
},
{
aspects = (
{
rating = 2;
type = overall;
}
);
"author_name" = "Victor Vu";
"author_url" = "https://www.google.com/maps/contrib/108347888656400683025/reviews";
language = en;
"profile_photo_url" = "//lh4.googleusercontent.com/-92ZhOOjfwfE/AAAAAAAAAAI/AAAAAAAAZqA/16j_bEwt6fk/photo.jpg";
rating = 4;
"relative_time_description" = "7 months ago";
text = "Great place to study... when it's quiet. There's enough light to clearly see your paperwork. Music is on the medium side and easy to phase out when you put on headphones, or focus really hard. It's really chill!\n\nFood and drinks are nice. It's a great place for coffee and deserts. Most people get waffles or ice cream here. Their matcha green tea is nice as well.\n\nThe only problem is that there is never enough seating during peak hours and it gets claustrophobic when it's crowded with people doing their homework.\n\nSeats are also cheap and hurt your butt.\n\nThis place usually attracts the Asian crowd.";
time = 1463551785;
},
{
aspects = (
{
rating = 1;
type = overall;
}
);
"author_name" = "Brian Melton";
"author_url" = "https://www.google.com/maps/contrib/103909645378067844462/reviews";
language = en;
"profile_photo_url" = "//lh4.googleusercontent.com/-enGmeXKqYC0/AAAAAAAAAAI/AAAAAAAA4eo/maQQGZ8Etdo/photo.jpg";
rating = 3;
"relative_time_description" = "a month ago";
text = "Expensive, small-sized coffees make this coffee shop feel like poor value-for-money. Limited food options but has great desserts: try the macaroons.";
time = 1480554027;
}
);
scope = GOOGLE;
types = (
cafe,
restaurant,
food,
store,
"point_of_interest",
establishment
);
url = "https://maps.google.com/?cid=6993426060231780804";
"utc_offset" = "-480";
vicinity = "611 Post Street, San Francisco";
website = "http://www.joysplacecafe.com/";
}, "status": OK]
And here is my code with a dummy query:
let url = URL(string: "https://maps.googleapis.com/maps/api/place/details/json?placeid=ChIJheCrEY6AhYARxCkT8_KjDWE&key=[MYAPIKEY]")
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) {
(data, response, error) in
guard error == nil else {
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let placeData = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments)
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("The placeData is: \(placeData.description)")
guard let website = placeData["website"] as? String else {
print("Could not get the website from JSON")
return
}
print("The title is: \(website)")
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
Any help understanding what's going on would be much appreciated. The final objective is to get the Opening hours a Dictionary with the keys being the days of the week and the values being the opening times.
Thanks in advance
The problem is that reponseData is a dictionary with a key response containing the actual dictionary you want. The JSON looks something like:
{
"result": {
"website": "example.com"
}
}
So the correct code would be:
let url = URL(string: "https://maps.googleapis.com/maps/api/place/details/json?placeid=ChIJheCrEY6AhYARxCkT8_KjDWE&key=[MYAPIKEY]")
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) {
(data, response, error) in
guard error == nil else {
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let placeData = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments)
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("The placeData is: \(placeData.description)")
guard let website = (placeData["result"] as? [String : Any])?["website"] as? String else {
print("Could not get the website from JSON")
return
}
print("The title is: \(website)")
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
I simply changed:
guard let website = placeData["website"] as? String else {
to:
guard let website = (placeData["result"] as? [String : Any])?["website"] as? String else {

Resources