{"id":10242,"date":"2014-12-06T10:20:35","date_gmt":"2014-12-06T15:20:35","guid":{"rendered":"http:\/\/mjtsai.com\/blog\/?p=10242"},"modified":"2015-06-24T18:08:15","modified_gmt":"2015-06-24T22:08:15","slug":"developing-keyboards-for-ios","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2014\/12\/06\/developing-keyboards-for-ios\/","title":{"rendered":"Developing Keyboards for iOS"},"content":{"rendered":"<p><a href=\"http:\/\/norbertlindenberg.com\/2014\/12\/developing-keyboards-for-ios\/\">Norbert Lindenberg<\/a> on what he learned developing his <a href=\"http:\/\/lindenbergsoftware.com\/english-ipa\">English IPA Keyboard<\/a> (<a href=\"http:\/\/mjtsai.com\/app\/ios\/english-ipa-keyboard\">App Store<\/a>):<\/p>\r\n<blockquote cite=\"http:\/\/norbertlindenberg.com\/2014\/12\/developing-keyboards-for-ios\/\">\r\n<p>Yes, some of these suggestions sound a little desperate, and for many keyboards you will at some point have to request full access. Things would be a lot easier if Apple allowed containing apps to write into their keyboards&rsquo;s containers, similar to how the Mail app delivers attachments into an app&rsquo;s Inbox directory, without the need for full access. This would let containing apps send dictionaries and configuration settings to the keyboard while protecting user input from being leaked out of the keyboard.<\/p>\r\n<p>[&#8230;]<\/p>\r\n<p>A number of keyboards have been rejected based on section 25.6, so it seems <em>something<\/em> is required. For the first version of the English IPA keyboard, I argued that the keyboard was designed as a supplementary keyboard, not as a user&rsquo;s primary keyboard, and therefore shouldn&rsquo;t have to support all the keyboard types, similar to Apple&rsquo;s emoji keyboard. My keyboard was approved, but at least one other developer using similar arguments saw his app rejected.<\/p>\r\n<p>[&#8230;]<\/p>\r\n<p>It&rsquo;s much better to design custom images for the function keys that your keyboard needs. There doesn&rsquo;t seem to be any way for third-party keyboards to access the images that Apple&rsquo;s built-in keyboards use.<\/p>\r\n<p>[&#8230;]<\/p>\r\n<p>Reading the <a href=\"https:\/\/developer.apple.com\/library\/ios\/documentation\/UIKit\/Reference\/UITextInputDelegate_Protocol\/index.html\">documentation<\/a> might lead you to expect that your input view controller&rsquo;s <code>textDidChange<\/code> method will be called when &ldquo;text has changed in the document&rdquo;, or <code>selectionDidChange<\/code> when &ldquo;the selection has changed in the document&rdquo;. Reality as of iOS 8.1 is that <code>textDidChange<\/code> and its buddy <code>textWillChange<\/code> are called when the keyboard&rsquo;s client view becomes or resigns as first responder and when the selection changes, and <code>selectionDidChange<\/code> and its peer <code>selectionWillChange<\/code> are never called. Of course, this might get fixed eventually, so don&rsquo;t rely on this either.<\/p>\r\n<p>[&#8230;]<\/p>\r\n<p>The documentation for <code>UIKeyInput.deleteBackward<\/code> states that it deletes &ldquo;a character&rdquo;. Those familiar with Unicode, <code>NSString<\/code>, and Swift&rsquo;s <code>String<\/code> type know that the word &ldquo;character&rdquo; is quite overloaded &#8211; it could mean a Unicode code point, a UTF-16 code unit, or an <a href=\"http:\/\/www.unicode.org\/reports\/tr29\/#Grapheme_Cluster_Boundaries\">extended grapheme cluster<\/a>. But that&rsquo;s only where the trouble starts.<\/p>\r\n<p>[&#8230;]<\/p>\r\n<p>Implementing forward deletion as described in the <a href=\"https:\/\/developer.apple.com\/library\/ios\/documentation\/General\/Conceptual\/ExtensibilityPG\/Keyboard.html#\/\/apple_ref\/doc\/uid\/TP40014214-CH16-SW11\">API Quick Start for Custom Keyboards<\/a> section of the Guide also does not work. It depends on the assumption that the increment used by <code>adjustTextPositionByCharacterOffset<\/code> matches what <code>deleteBackward<\/code> deletes, but in reality that&rsquo;s not always the case.<\/p>\r\n<p>[&#8230;]<\/p>\r\n<p>It seems unlikely that you could invade the user&rsquo;s privacy by playing click sounds when she taps a key, but the method in charge of these sounds, <code>UIDevice.playInputClick<\/code>, hangs for several seconds rather than doing its job when called without full access.<\/p>\r\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>Norbert Lindenberg on what he learned developing his English IPA Keyboard (App Store): Yes, some of these suggestions sound a little desperate, and for many keyboards you will at some point have to request full access. Things would be a lot easier if Apple allowed containing apps to write into their keyboards&rsquo;s containers, similar to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"","apple_news_api_id":"","apple_news_api_modified_at":"","apple_news_api_revision":"","apple_news_api_share_url":"","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[130,200,1079,948,905,31,904,423,71,901,258],"class_list":["post-10242","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-rejection","tag-autolayout","tag-english-ipa-keyboard","tag-english-language","tag-extensions","tag-ios","tag-ios-8","tag-keyboard","tag-programming","tag-swift-programming-language","tag-unicode"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/10242","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=10242"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/10242\/revisions"}],"predecessor-version":[{"id":11570,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/10242\/revisions\/11570"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=10242"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=10242"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=10242"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}