tag:blogger.com,1999:blog-49997180791202152682024-03-05T07:05:30.589+00:00Vladimir Kelin - DevelopmentBlog about iOS development. Code snippets, best practice, discussions, stories, jokes and tutorials - everything that I can't post on Stackoverflow.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.comBlogger18125tag:blogger.com,1999:blog-4999718079120215268.post-65533073812645020322019-03-11T20:20:00.002+00:002019-03-11T20:26:21.075+00:00How to Record Calls on iPhoneHere is another way involving a Mac and EarPods.</ br></ br>
Attach EarPods to the Mac. Then attach the remote controller of the EarPods (which contains a microphone) to the iPhone. The microphone should be attached near the speaker. To fix it on the phone you can use a tape or a bulldog clip. Or just clamp it under the cover:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiknC8oHo7Oc4XVV9O3okg6klJKwGQnL37bVsY7UXggKFyUB9cRaas29TvOSR1S0RJaIIlDMZt6AHDgLuQrDOT34U2VDPUYGZiUCpjnmSim9x1RDW_XSFSG0Qo8GgeWuaLaOyjgKe8_-XQ/s1600/photo.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiknC8oHo7Oc4XVV9O3okg6klJKwGQnL37bVsY7UXggKFyUB9cRaas29TvOSR1S0RJaIIlDMZt6AHDgLuQrDOT34U2VDPUYGZiUCpjnmSim9x1RDW_XSFSG0Qo8GgeWuaLaOyjgKe8_-XQ/s400/photo.JPG" width="400" height="315" data-original-width="1600" data-original-height="1258" /></a></div>
Use QuickTime to create a sound file. Click File > New Audio Recording. Since you attached your headphones to the Mac, the External microphone will be selected by default.
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht0gjUXxcR6bGCisCixcIHuZw0OaVn0Td3RHLJcAZrz28OFgnoGdbWdsP9Nz6zhieU9vnUXWRy0p441_y-QVYTD4-yAeu9ivZZ5P2H1vPKE-der8t-94qQ0AGPG3mo4JrQpkhKC8Wt6Zo/s1600/quick+time.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht0gjUXxcR6bGCisCixcIHuZw0OaVn0Td3RHLJcAZrz28OFgnoGdbWdsP9Nz6zhieU9vnUXWRy0p441_y-QVYTD4-yAeu9ivZZ5P2H1vPKE-der8t-94qQ0AGPG3mo4JrQpkhKC8Wt6Zo/s1600/quick+time.png" data-original-width="788" data-original-height="448" /></a></div>
Then hit the red button to start the recording. Make a call.</ br></ br>
The described technique <b>doesn't require speaker mode</b> and gives a good sound quality without noises. Your mouth is farther from the EarPods micro than the speaker of the iPhone. The distance compensates the differences in the sound volume, thus the both voices in the call will have similar loudness.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com1tag:blogger.com,1999:blog-4999718079120215268.post-64619648338601815122019-02-19T18:45:00.000+00:002019-02-19T18:45:54.696+00:00Where Smart Watches Gone Wrong<h1>Could it be better?</h1>
It would be, if exterior and interior of smartwatches was produced by different companies. The idea is to specialize. The hulls and bands could be made by a classic watch manufacturers and fashion houses. Аnd the smart parts, like hardware and software should be made IT companies, like Apple, Samsung, Xiaomi or Microsoft. Well-known watch brands like Louis Vuitton, Rolex or Omega produce luxury goods, they haven't enough resources and expertise to create their own hardware design or operating system. IT giants, in turn, being oriented on broad market and unified products, couldn't focus on individual design and bring such huge and artistic variety of looks. Even in "gold edition".
<h1>Benefits</h1>
What would we, the customers, get from such change in the industry?
<ol>
<li>Nicer watches.</li>
<li>Higher flexibility.</li>
<li>Lower price of the update (if the old body used with new electrical parts. The watch body can even go from father to son!)</li>
</ol>
<h1>Examples</h1>
What will happen if watches turn the correct way? Some examples:
<ol>
<li>A G-Shock-like Apple Watches. A sports watch in robust shock resistant shell. Awesome? Awesome.</li>
<li>Beautiful and expensive watches like Blancpain. With all new updates.</li>
<li>Retrofitting. You'll be able to update an old grandfarther's watch with a new powerful hardware.</li>
</ol>
Will it ever happen?Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-37646140531727439052018-10-28T20:30:00.002+00:002018-10-28T20:30:55.223+00:00How to get old WWDC videosIt appears that <b>old WWDC videos are removed</b> from <a href="https://developer.apple.com/videos/all-videos/">the official list</a>. This post tells how to get them.<br />
So, if you visit the developers's site from the above link you will see that currently available year range is only 2015–2018:
<div class="separator" style="clear: both; text-align: center; margin: 2em 0;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYdBeDjdNwJ5ARhq46CS2bkMLPlen5LkisN_qSW86nwf8ab4pPbzb5sPSZkrl_K72gYX4eHgazYt5XDm-C3-B52AEhseXf6la-boUmSrn3Wb0elTY7RzSVI3Tgaw1bfGqEb35-sbW7SSU/s1600/years.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYdBeDjdNwJ5ARhq46CS2bkMLPlen5LkisN_qSW86nwf8ab4pPbzb5sPSZkrl_K72gYX4eHgazYt5XDm-C3-B52AEhseXf6la-boUmSrn3Wb0elTY7RzSVI3Tgaw1bfGqEb35-sbW7SSU/s640/years.png" width="640" height="240" data-original-width="1600" data-original-height="599" /></a></div>
Even the search yields no results:
<div class="separator" style="clear: both; text-align: center; margin: 2em 0;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgruo-UsyoMUVud3sSbAtofkbV_Dzc3uRAjMGN2fuECjuqTJxo-WU8S2xduiIEGvBZUhV7JesL1u6DbRQmaAQ5xv-uWlR9ADrcMv11_vw9iuayEfuUH5YsBgmoUPtJCzIgCd_FGcfdMAwU/s1600/no+found.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgruo-UsyoMUVud3sSbAtofkbV_Dzc3uRAjMGN2fuECjuqTJxo-WU8S2xduiIEGvBZUhV7JesL1u6DbRQmaAQ5xv-uWlR9ADrcMv11_vw9iuayEfuUH5YsBgmoUPtJCzIgCd_FGcfdMAwU/s640/no+found.png" width="640" height="198" data-original-width="1600" data-original-height="496" /></a></div>
I had such a problem trying to find 8 year old video (WWDC 2010, <i>Core Animation in Practice</i>). Thanks to <a href="https://www.reddit.com/r/swift/comments/9s1cez/apple_deleted_wwdc_videos_2014_c_where_can_i_find/">reddit community help</a>, several solutions were found:<br />
<ol>
<li>WWDC videos <b>prior to 2012</b> can be found in <a href="https://developer.apple.com/videos/archive/">Developer Video Archive</a>. </li>
<li><b>From 2012 to 2015</b> they can be accessed by the link: <a href="https://developer.apple.com/videos/wwdc2012/">https://developer.apple.com/videos/wwdc2012/</a>. Just change the year in the end.</li>
<li>The <b>keynotes</b> can be found <a href="https://itunes.apple.com/podcast/apple-keynotes-hd/id470664050?mt=2">in Podcasts</a>.</li>
</ol>
Thats all!Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-7957520447829489112018-10-18T21:46:00.000+01:002019-02-20T21:40:33.394+00:00UIKit protocol of the month: StoryboardInstantiatableViewController<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<a href="https://gist.github.com/kvaDrug/e1d88092f38eb38b0c6cd91a6f7be590">Github Gist.</a><br /><br />
I want to share this little invention with the community. I use it for the last month and find it notably helpful:</ br>
<code class="prettyprint lang-swift block">protocol StoryboardInstantiatableViewController {
static var storyboardId: String { get }
}
extension StoryboardInstantiatableViewController where Self: UIViewController {
static func instantiate(from storyboard: UIStoryboard?) -> Self? {
return storyboard?.instantiateViewController(withIdentifier: Self.storyboardId) as? Self
}
}</code><br />
This protocol along with the extension provides some syntactic sugar and solves two common problems:
<ul>
<li>Shortens the instantiation</li>
<li>Answers, where to store the storyboard id</li>
</ul>
<h3>Usage example:</h3>
<code class="prettyprint lang-swift block">// Declaration:
class TaskViewController: UIViewController, StoryboardInstantiatableViewController {
// The same id as specified in Identity inspector
static let storyboardId: String = "task"
// ...
}
// Initializing from Storyboard:
// ...
guard let taskVC = TaskViewController.instantiate(from: storyboard) else { return }
navigationController?.pushViewController(taskVC, animated: true)</code><br />
<h3>Update:</h3>
Surprisingly, I bumped into another similar <a href="https://gist.github.com/usagimaru/9b6b52ba1ab29018275575d8ce7a9b48">protocol</a>, named <code>StoryboardInstantiatable</code>. The main difference that it's designed for use with <code>NSViewController</code> and Cocoa instead of Cocoa Touch. The fact is, that the gist was posted <b>5 months earlier</b> than this blog post. So Satori Maru is the first one who invented the idea, although I came to it independently.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-27428208571140564932018-08-22T20:05:00.000+01:002018-08-22T20:05:20.028+01:00Errors in WWDC 2018<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4TofMQH1xeMIfqqDumAIcCxVE7LJW1dNN9WmgWf0OOufeCao47WrV70x3cgnNdifO12MLGQsQ68mM2SG54Z5dIV32QfS3RmHbRZQTpAiIyItZg4KLCCUTrk524AXTiZMzH8SUqz3z2fQ/s1600/errors_wwdc18_404.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4TofMQH1xeMIfqqDumAIcCxVE7LJW1dNN9WmgWf0OOufeCao47WrV70x3cgnNdifO12MLGQsQ68mM2SG54Z5dIV32QfS3RmHbRZQTpAiIyItZg4KLCCUTrk524AXTiZMzH8SUqz3z2fQ/s1600/errors_wwdc18_404.png" alt="WWDC 2018, Session 404, Slide 34" style='height: 100%; width: 100%; object-fit: contain' /></a>
Some errors in the translation example. <a href="https://developer.apple.com/videos/play/wwdc2018/404/">WWDC 2018, Session 404: New Localization Workflows in Xcode 10</a>, slide 34. Looks like additional words were added just to make EN->RU translation look more complicated :)Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-81900497335919071162018-08-14T20:35:00.002+01:002019-03-12T06:15:29.126+00:00Sorting and Choosing Keywords for App Store<i>Updated: 12 Mar 2019.</i><br /><br />
Suppose you already have plenty keyword ideas for your app. Or, at least, you understand <a href="https://developer.apple.com/app-store/search/">how to select them</a>.<br /><br />
There are so many promising keywords, but in App Store their total length is limited to 100 bytes including commas. This post describes a sensible way to select a few keywords which will give the best search performance.
<h3>Organize Keywords in a Spreadsheet</h3>
Additionally, suppose that the target app is named Plant Classifier and it's a <span style="color:#483D8B">visual recognition</span> <span style="color:#800000">deep</span> <span style="color:#008080">machine learning</span> <span style="color:#A0522D">neural network</span> <span style="color:#FF8C00">startup</span> based app, allowing users to identify a plant by it's photo. Here's its icon:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ZEMe3ajWOo_8wf63knisWFM6LRzUaItbIdKpFSIuwEi6EqWELWzFpcTFicnHSeAYvm_RbWi0V7Evw97LZVzp2XcYPtiQii6DKmnfE8SHotPmHbsEcGvWnpnKM9l0Z_EKu88WadPb5ng/s1600/PlantClassifierIcon-01.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ZEMe3ajWOo_8wf63knisWFM6LRzUaItbIdKpFSIuwEi6EqWELWzFpcTFicnHSeAYvm_RbWi0V7Evw97LZVzp2XcYPtiQii6DKmnfE8SHotPmHbsEcGvWnpnKM9l0Z_EKu88WadPb5ng/s1600/PlantClassifierIcon-01.png" data-original-width="512" data-original-height="512" /></a></div>
(it's the kenaf and not what you think)<br /><br />
Your keyword ideas are: <code>taxonomy, systematics, species, genus, name, identifier, identify, determine, search, herbarium, botany, nomenclature, photo, picture, image, atlas, visual, recognition, analysis, flower, tree, shrub, weed, herb, garden, leaf</code>. Their total length without whitespaces will be 200. It's actually a good practice to have at least twice as more words than you need, before you started to cull them.<br /><br />
You think the app page with such keywords will easily match searches like:<br />
<ul>
<li><code>plant species</code></li>
<li><code>identify tree genus</code></li>
<li>or <code>determine flower by picture</code></li>
</ul><br />
Now do the first simple yet important step: create a spreadsheet. Make a column named <code>Keywords</code> and paste all 26 ideas there.
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5UeOpDg6WQEQ7X3K5Pja8fYF2vqVozlufIZ3Wi9wlEKHhtjy42vJvfirLXFmy223wgebJQH0ywpLWjrLx1_iLpoJPxwYRUTmxgit9Ymsihy3udbNtdfuirTtOewN3PWM0sYURQRYZ10w/s1600/Keywords.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5UeOpDg6WQEQ7X3K5Pja8fYF2vqVozlufIZ3Wi9wlEKHhtjy42vJvfirLXFmy223wgebJQH0ywpLWjrLx1_iLpoJPxwYRUTmxgit9Ymsihy3udbNtdfuirTtOewN3PWM0sYURQRYZ10w/s1600/Keywords.png" data-original-width="416" data-original-height="958" /></a></div>
The spreadsheet will allow you to store keywords data in the convenient way and finally sort it. I prefer <a href="https://docs.google.com/spreadsheets/u/0/">Google Sheets</a> but any other tool will meet our demands.
<h3>Analyse App Store Search Results</h3>
The next thing you do is literally "go to App Store and search apps using each of the keyword candidates". Sure, this is the most precise way to evaluate the <b>concurrency</b>. The most competitive words will evoke more search results. Luckily we don't need to do this manually, because we have <a href="https://github.com/kvaDrug/Keywording">Keywording</a> utility based on <a href="https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/">iTunes Search API</a>.<br /><br />
For example, the request:<br />
<code class="block">./keywording "taxonomy,species" NZ</code>
will give:
<code class="block">NZ
species 164
taxonomy 26</code><br />
Create a column: <code>Concurrency, App Store results</code>. Set the country to more popular US store and perform search for all the words, filling the newly created column:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyXPIvfMGJOZnjTe3M2GqvbPeJjxxLXqRN9V5A65teeKKKLCLdMzPWwKGQXe3BVojwkjvJkEubNImBse5LNEK2SN65y6S0RkhI2G6GsdaGPGWR4u99WG_hLTsISjILS-g2ExegvvwbjkQ/s1600/Concurrency.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyXPIvfMGJOZnjTe3M2GqvbPeJjxxLXqRN9V5A65teeKKKLCLdMzPWwKGQXe3BVojwkjvJkEubNImBse5LNEK2SN65y6S0RkhI2G6GsdaGPGWR4u99WG_hLTsISjILS-g2ExegvvwbjkQ/s1600/Concurrency.png" data-original-width="500" data-original-height="956" /></a></div><br />
<h3>Estimate Search Volume</h3>
To estimate the <b>search volume</b> we will use <a href="https://adwords.google.com/aw/keywordplanner/home">Google Ads Keyword Planner</a>. It can tell, how many searches users perform in "Google and search partners" network, and again, it's not the data we desire, but we assume, that Google searches will be quite close to the App Store ones. After you open the Planner, follow this steps:<br />
<ol>
<li>Select "Get search volume and forecasts" box.</li>
<li>Paste keywords there, and click GET STARTED.</li>
<li>You will see the results under FORECAST section.</li>
</ol>
The current version of Keyword Planner provides Clicks and Impressions - both metrics are characterising the volume. But I recommend to use Impressions because it's more stable. In our case words <code>photo</code>, <code>picture</code> and <code>image</code> have enormously heightened clicks.<br /><br />
Other recommendations: don't specify any particular location to enlarge input data (if your app isn't country-bound), but specify the language. Best time range is "next month".<br /><br />
Now set English language and write Impressions to the spreadsheet under <code>Impressions, Google</code> column. You will get something like this:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhluKoobv1QCZv1D8ExXhISy6XKwTwUiEnIT4JGQ9jQiLgPNUtPkyyWKOCBErK9rbAESU-PysUHHvqyKMqvMkNeSwCtnj3yqx7cyangGP8LAhpIhm-EShre3dyOwQDKJGYRudmbJj9y0hs/s1600/Impressions.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhluKoobv1QCZv1D8ExXhISy6XKwTwUiEnIT4JGQ9jQiLgPNUtPkyyWKOCBErK9rbAESU-PysUHHvqyKMqvMkNeSwCtnj3yqx7cyangGP8LAhpIhm-EShre3dyOwQDKJGYRudmbJj9y0hs/s1600/Impressions.png" data-original-width="650" data-original-height="948" /></a></div>
<h3>Determine Linguistic Frequency</h3>
<b>Linguistic frequency</b> is especially helpful with languages you are not very good at. If you found a couple of synonyms for a keyword, it can help you to find which one is more common to native speakers. Also, the frequency may help you to make a decision when the other indicators are equal. Generally, this step is optional and can be skipped.<br /><br />
But in the educational purpose you will determine the linguistic frequency of each keyword using <a href="https://books.google.com/ngrams">Ngram</a> (so many great tools by Google!). Paste first 12 comma-separated words in the search field, check "case-insensitive", select the 3 last years (currently the freshest data are from 2008, so select 2005-2008 range) and hit the Enter. Now you can take a screenshot of the values:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq6Bbp0rTDN9tRVd745lkwQDKWoCyDus-iWLsvC0i-j0QN1Ex22Y0Mag8qk51YurGwCVj-h6yqfBARjb7ypy8wJ30SuOid4meCw2ShJ9IbV6aTh08YsDdg-XXRf6YwYeDCAP3PaIxjCL0/s1600/Ngram+snap.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq6Bbp0rTDN9tRVd745lkwQDKWoCyDus-iWLsvC0i-j0QN1Ex22Y0Mag8qk51YurGwCVj-h6yqfBARjb7ypy8wJ30SuOid4meCw2ShJ9IbV6aTh08YsDdg-XXRf6YwYeDCAP3PaIxjCL0/s1600/Ngram+snap.png" data-original-width="564" data-original-height="414" /></a></div>
and after spending some time while putting them into the <code>Ngram, frequency</code> column of the table, you will get:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVcQ94-HJH01LaH1NjYJlGyeGbb_qWYDHPnQvxyGG6BZB6Yr6hyphenhyphen6HJ3JXkBBamn_ztwtZCYThFV6UpvivCySd7dX6btOZiKHsPcCwv9ymCfsr8wmQGunbSFKEpix6xYEAzykSiF00XUVk/s1600/Frequency.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVcQ94-HJH01LaH1NjYJlGyeGbb_qWYDHPnQvxyGG6BZB6Yr6hyphenhyphen6HJ3JXkBBamn_ztwtZCYThFV6UpvivCySd7dX6btOZiKHsPcCwv9ymCfsr8wmQGunbSFKEpix6xYEAzykSiF00XUVk/s1600/Frequency.png" data-original-width="834" data-original-height="954" /></a></div>
<h3>Sorting and Choosing</h3>
Time for the final part for which we were preparing since the beginning of the article. Analyse your data. First, look at the Concurrency column. Most of the keywords have values < 200 and that means that the competition is sufficiently low. You likely to choose some of them. Now select all the data and sort it by Impressions, decreasing. Have a look at it:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdTxRZNwJE3USMU7J48qxMaS6TIsdrqt3fLXXutpXLMdS8wq4e6NvZD-afDsnlV5bZA6qRzC1B63cuYcM-2wVCqJ6PHSbpWj2do-E_mwLccEiNJARwFbn9uj5oS-MP919bmUZ3q-gQ264/s1600/Sorted+by+Impressions.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdTxRZNwJE3USMU7J48qxMaS6TIsdrqt3fLXXutpXLMdS8wq4e6NvZD-afDsnlV5bZA6qRzC1B63cuYcM-2wVCqJ6PHSbpWj2do-E_mwLccEiNJARwFbn9uj5oS-MP919bmUZ3q-gQ264/s1600/Sorted+by+Impressions.png" data-original-width="838" data-original-height="956" /></a></div>
Noticed the words with low concurrency but high search volume? They are "golden", you choose them, definitely. First five are highlighted with yellow: <code>garden, tree, leaf, name, herb</code>. Their total length is only 26. You need sufficiently more.<br /><br />
If we select all words with low competition, we will obtain the following: <code>garden, tree, leaf, name, herb, analysis, shrub, recognition, herbarium, botany, visual, taxonomy, genus, species, identify, nomenclature, determine, systematics</code> (total length - 144). Some words have to be dropped.<br /><br />
Now sort the table by Concurrency, increasing. What do you see there?
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdu4lKhD4vODuthLH4-tZXaUgBB2A9Vh1-vIU76Uesx6Uqrm6pVgPRoPVEaHeSydFkTewF2M3a5hFgsfWBGvoSm5-sfMvV-XIG8h0ekOPG9bzPgA5_2tZUKXQBMY2d_jNIezBwzMSI3lc/s1600/Sorted+by+Concurrency.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdu4lKhD4vODuthLH4-tZXaUgBB2A9Vh1-vIU76Uesx6Uqrm6pVgPRoPVEaHeSydFkTewF2M3a5hFgsfWBGvoSm5-sfMvV-XIG8h0ekOPG9bzPgA5_2tZUKXQBMY2d_jNIezBwzMSI3lc/s1600/Sorted+by+Concurrency.png" data-original-width="822" data-original-height="912" /></a></div>
Some quite unpopular, low-concurrent yet meaningful in the context of your app words, like: <code>herbarium, taxonomy, botany, shrub</code>, they are silver words (blue). If you need to drop some words, you better leave them.<br /><br />
Another interesting group is so-called red words which are popular, highly concurrent but closely describe your app. Such as <code>flower, image, picture, photo</code>. Consider them if you need to add some words to the final selection.<br /><br />
So, suppose, you drop <code>analysis, nomenclature, systematics, determine</code> (not very suitable, anyway). Now we you have exactly 100 characters: <code>garden, tree, leaf, name, herb, shrub, recognition, herbarium, botany, visual, taxonomy, genus, species, identify</code>. So neat!
<h3>Other Considerations</h3>
But could it be neater? There is still a place for the <b>art</b> in the keyword optimisation.<br /><br />
Plurals can be replaced with singulars. The search algorithm will treat them the same. This also true for verb vs. noun forms and for typos. So, you can replace <code>recognition</code> with <code>recognize</code> saving 2 characters. This information isn't robust, in practice it works for English keywords, but keep in mind that Apple may change their algorithms any time.<br /><br />
Choose not only keywords, but their combinations. For example <code>garden</code> keyword is very sweet according to our research. But, how folks supposed to use it? Make some lame request like <code>app for garden</code>? You shouldn't rely on that. Instead, take at least one of "red" image-related words, let's say ... <code>image</code>.<br /><br />
BTW, you probably already noticed that <code>weed</code> is somehow super-popular in the App Store, but has 0 impressions according to Google. It's the worst possible indicator. How could it be? If we open App Store and search for this keyword, we will see that store is literally spammed with trashy apps, which are unrelated to our topic. You better avoid such keywords if you want your app to be age rated 4+.<br /><br />
In conclusion, we've got 14 most efficient keywords of total length 97 characters without whitespaces: <code>image, tree, leaf, name, herb, shrub, recognize, herbarium, botany, visual, taxonomy, genus, species, identify</code>. And most important, you learned predictable (almost scientific!) way to gauge keywords, to finally make a sensible selection which will elevate your app to the top of the targeted search results.<br /><br />
The example spreadsheet is available in <a href="https://docs.google.com/spreadsheets/d/e/2PACX-1vRdocUPWA3Sp7HHSwTklJUssaSGs5X10vDNvtqQa3Wi9Rq55yumziSQ8IdLaLusgPfTv46lURRv0vl8/pubhtml">HTML</a> and <a href="https://docs.google.com/spreadsheets/d/1LakziC-u8R5jkMlR6zA8hCOnD3x107BKUukhtUO30FA/edit?usp=sharing">Google Sheets</a>.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-16908233324970261012018-07-19T10:12:00.003+01:002018-07-27T21:09:52.659+01:00How to move View Controllers on the Storyboard?<a href="https://stackoverflow.com/q/51418643/3050403">Question and answer on Stackoverflow.</a>Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-62769625026034904912018-07-10T23:34:00.003+01:002018-07-10T23:34:56.776+01:00Support Story: "Pls, Make a Tutorial"As an individual developer, I convinced to fulfil support job for my customers. Each app in App Store should have a support link. For this purpose I use Google Forms (free hosting, pretty convenient). <a href="https://itunes.apple.com/us/app/periodic-timer/id933241656">Periodic Timer</a> also has a <a href="https://www.facebook.com/groups/PeriodicTimer/">Facebook group</a>. And that's amazing, that people really tend to write questions and suggestions to me! Though there always a percent of offtopic. I think the regular sharing of funny conversations is something that may happen on this blog.<br /><br />
And today's hero (call him Wob for anonymity) was someone, who pretty obstinately wanted me tutorial to the Periodic Timer (which is totally simple and intuitive app, BTW). He wrote directly to my support email and we had this funny conversation:<br /><br />
<i>Wob <wobbbb@gmail.com><br />
Feb 14<br />
to me <br />
I just downloaded Periodic Timer but can find no instructions on how to use and fine-tune it. <br />
Can I set the sound it makes or the volume, for example?<br />
Do you have a set of instructions you can email me?<br />
Thanks.<br />
<br /><br />
Periodic Timer Support <periodictimer.sup@gmail.com><br />
Feb 14<br />
to Wob <br />
Periodic Timer is simple and intuitive app that doesn't provide complicated options and can be can be understood without any instructions.<br />
Change volume of Periodic Timer like you change like you do it with Alarm or Ringtone.<br />
Good Luck :)<br />
<br /><br />
Wob <wobbbb@gmail.com><br />
Feb 14<br />
to me <br />
If it were as simple and intuitive as you claim, I wouldn’t have asked you for basic instructions. Please explain. (I can’t be the only person who’s asked for this information.)<br />
<br /><br />
Periodic Timer Support <periodictimer.sup@gmail.com><br />
Feb 14<br />
to Wob <br />
You are :)<br />
How about the first link from the google? https://www.wikihow.com/Adjust-the-Volume-on-iOS-10<br />
Have a nice day.<br />
<br /><br />
Wob <wobbbb@gmail.com><br />
Feb 15<br />
to me <br />
I think you misunderstand. I didn’t want just info about volume.<br />
How do I use your app: 1, 2, 3? It’s too minimalist for me.<br />
<br />
Thanks.</i><br /><br />
And then the story continues, tutorial-guy creates a new email for disguise, something like w.wob@gmail.com, and continues to solicit me for tutorial. I answered quite roughly:<br /><br />
<i>
Just look at yourself:<br />
<br />
I can’t figure out how to successfully use your app. It’s very frustrating. Can you make a better tutorial? Also, I’ve already upgraded but i keep getting reminders to upgrade again. Please fix these bugs<br />
<br />
You are creating multiple emails and spam me with "ples make a tutorial" messages. Why do you even do this? I suppose you are one of retired mans who has plenty of free time but no one to talk to. <br />
<br />
I want to tell you one simple thing: this app is my hobby so, please, don't wait I will run around you trying to satisfy any of your whims. I have no free time, and also this app doesn't brings any money at all. <br />
<br />
So, I hope you quite clever to understand me, if not - don't be surprised why I ignore you.</i><br /><br />
Still not sure, was that an attempt to flirt with me?Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-74557442180985246842018-06-30T21:29:00.000+01:002018-06-30T21:29:28.308+01:00Handling background touches: UIControl instead of UITapGestureRecognizer<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
Usually, to handle <b>background touches</b> on a View Controller we add a <code class="prettyprint lang-swift">UITapGestureRecognizer</code>. The setup requires a few lines of code, but there is a way, you can handle background touches even more easy!<br /><br />
What you need to do is just to change the root view type from <code class="prettyprint lang-swift">UIView</code> to <b><code class="prettyprint lang-swift">UIControl</code></b> and attach an action. With <b>Storyboard</b>:
<ol>
<li>
Select the root view.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilFcvZ8PbRRAnVY-z9p3beVc7mHLF6i_LbPY0KZRrXfybqJeOtlrucjckCdVrvrDdW3dhUX7XzNThu6q1bqfbf3zPHf_SxKYLwXmpth8YJ2du5nEjDfs3cQPTdcjeuOwLgOXV6dLU-S6E/s1600/select+view.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilFcvZ8PbRRAnVY-z9p3beVc7mHLF6i_LbPY0KZRrXfybqJeOtlrucjckCdVrvrDdW3dhUX7XzNThu6q1bqfbf3zPHf_SxKYLwXmpth8YJ2du5nEjDfs3cQPTdcjeuOwLgOXV6dLU-S6E/s1600/select+view.png" data-original-width="530" data-original-height="166" /></a></div>
</li>
<li>
In the Identity inspector simply change the type to <code class="prettyprint lang-swift">UIControl</code>. (<a href="https://kelindev.blogspot.com/2018/02/changing-views-type-on-storyboard.html">Serious type change</a> isn't required.)<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIpyQHyr5x6cxUskjnUAvSbJ1j_twrzgWAdbkVt-gTRVzudEqQON1eKZOXX8Gx46FNJsUhVGqPErM3sDBRnGc5L_8x3-sBjk2NaGArcPyjentYxCR5PIdUj2eixjoKgA9gx8pIAeU6LAE/s1600/change+type.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIpyQHyr5x6cxUskjnUAvSbJ1j_twrzgWAdbkVt-gTRVzudEqQON1eKZOXX8Gx46FNJsUhVGqPErM3sDBRnGc5L_8x3-sBjk2NaGArcPyjentYxCR5PIdUj2eixjoKgA9gx8pIAeU6LAE/s1600/change+type.png" data-original-width="518" data-original-height="254" /></a></div>
</li>
<li>
Finally, connect an action to the Touch Down event using Connections inspector.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4yqALWiY0ZAnH3oB_Qbmh7YwMxcul9dcYAB9ORw-RrmS10qXylsZidGOEpYT82vDbW1R4FLzAy6OnMu1wdXqrKc_oEhpuWNzr2RHnPOzClGrodebIahvjLv-DGwEbpoGNJa1FPkPli_8/s1600/connect+action.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4yqALWiY0ZAnH3oB_Qbmh7YwMxcul9dcYAB9ORw-RrmS10qXylsZidGOEpYT82vDbW1R4FLzAy6OnMu1wdXqrKc_oEhpuWNzr2RHnPOzClGrodebIahvjLv-DGwEbpoGNJa1FPkPli_8/s1600/connect+action.png" data-original-width="776" data-original-height="118" /></a></div>
</li>
</ol>
Then the action can be utilized, for example, to hide a keyboard:
<code class="prettyprint lang-swift block">@IBAction func backgroundTouched(_ sender: Any) {
// Hide keyboard
view.endEditing(true)
}</code><br />
If any child control interrupts a touch, it wouldn't be delivered to the background control! This is the main pro and con of this method. Sometimes it's exactly the desired behaviour to ignore touches on buttons. But other times you need to handle all the touches anywhere in the View Controller, like if you have a large UITextView and you want to hide the keyboard by tap.
Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-73542077599139859622018-06-04T21:08:00.001+01:002018-06-04T21:08:56.068+01:00How to convert HTML to NSAttributedString?<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
Well, simply don't. <b>Use <a href="https://developer.apple.com/documentation/webkit/wkwebview">WKWebView</a> instead.</b><br />
This will allow you to insert pictures, reuse CSS-styles, execute scripts etc.
<h3>Wait, why not to use NSAttributedString?</h3>
While Apple provides several methods to initialize an instance of NSAttributedString from an HTML data, like: <a href="https://developer.apple.com/documentation/foundation/nsattributedstring/1535412-init"><code class="prettyprint lang-swift">init(data:options:documentAttributes:)</code></a>, they are all extremely slow, about 1000 times slower than WKWebView which is as fast as Safari itself. Additionally, NSAttributedString supports a limited set of HTML attributes.<br /><br />
You may find many third-party solutions on GitHub (<a href="https://github.com/Cocoanetics/DTCoreText">DTCoreText</a> and many others). They can be faster, then the built-in converter, but still slower comparing to WebKit. I tried some, and they all were buggy and most are poorly supported after iOS7 release brought those "initWithHTML" methods.<br /><br />
The only <b>advantage of NSAttributedString</b> is the ability to put it in UI elements like UILabel or UIButton.
<h3>WKWebView example</h3>
Here follows super-advanced example demonstrating widest possible abilities of displaying rich and interactive text with HTML, CSS, and JavaScript. To make things <i>convenient</i> and <i>reusable</i> the HTML document is split into parts:<br /><br />
<h4>Tutorial_main.html</h4>
The main HTML that contains necessary elements that supposed to be involved in every page of the <a href="https://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html">tutorial</a>. Including:
<ul>
<li>A <code class="prettyprint lang-html"><head></code> block containing:
<ul>
<li>A raw CSS <code class="prettyprint lang-html"><style></code> section, containing easy-configurable constants.</li>
<li>An external CSS <code class="prettyprint lang-html">"stylesheet"</code>.</li>
<li>The extremely important <code class="prettyprint lang-html">"viewport"</code> <code class="prettyprint lang-html"><meta></code> tag, required to scale content of a WKWebView correctly.</li>
<li>A <code class="prettyprint lang-html"><script></code> tag that provides Tutorial.js script. Not important enough to be listed here.</li>
</ul>
</li>
<li>A customisable <code class="prettyprint lang-html"><body></code> section.</li>
</ul>
<code class="prettyprint lang-html block"><html>
<head>
<style type="text/css">
:root {
--softTextColor: %@;
--darkTextColor: %@;
--actionColor: %@;
--backgroundColor: %@;
--bodyFontSize: %@;
--titleFontSize: %@;
}
</style>
<link rel="stylesheet" type="text/css" href="Tutorial.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="Tutorial.js"></script>
</head>
<body>
%@
</body>
</html></code>
The <code class="prettyprint lang-swift">%@</code> format specifiers will be replaced later with strings. Tutorial.css and Tutorial.js are contained in the Main Bundle root folder, so you don't need to specify <code class="prettyprint lang-swift">baseURL</code> to the WKWebView. Yes, you can refer local files, even use anchor links.<br /><br />
<h4>Tutorial.css</h4>
<code class="prettyprint lang-css block">body {
color: var(--softTextColor);
font-family: "-apple-system";
font-size: var(--bodyFontSize);
margin-top: 0px;
text-align: left;
-webkit-user-select: none;
-webkit-touch-callout: none;
background-color: var(--backgroundColor);
}
h1 {
text-align: center;
font-size: var(--titleFontSize);
font-weight: 300;
color: var(--darkTextColor);
margin-top: 0.51em;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
}
a {
color: var(--actionColor);
text-decoration: none;
}</code>
It's handy to keep CSS in a separate file. You can see some <i>constants</i> which already were listed before.<br /><br />
<h4>step2_body.html</h4>
<code class="prettyprint lang-html block"><h1>Сonnection</h1>
<p>Connect your device to your computer using the USB cable.</p>
<div class="spoiler">
<p>You can sync the device with iTunes using Wi-Fi.
In this case you will need the USB cable only once: to activate wireless syncing.
<a href="https://support.apple.com/en-us/HT203075">Read more.</a></p>
</div>
<div class="centered">
<a class="showMoreButton" href="showmore://">
<img src="show_more.svg" width=20 height=15 />show more
</a>
<a class="showLessButton" href="showless://">
<img src="show_less.svg" width=20 height=15 />show less
</a>
</div></code>
The body of the second step. Look at two buttons, there is something interesting about them. First, they use <b>.svg</b> images, because WKWebView supports vector graphics. (Images are also placed in the root directory of the Main bundle). Also, buttons use custom-scheme anchors, e.g.: <code class="prettyprint lang-html">href="showmore://"</code>. These anchors allows to intercept button clicks on the Swift-side using the <code class="prettyprint lang-swift">webView(,decidePolicyFor:,decisionHandler:)</code> method of <a href="https://developer.apple.com/documentation/webkit/wknavigationdelegate">WKNavigationDelegate</a>.<br /><br />
<h4>Assemble</h4>
Time to put all the things together.
<code class="prettyprint lang-swift block">func assembleHTMLString(bodyFileName: String) -> String? {
// All resources are contained in the Main Bundle
let bundle = Bundle.main
guard let mainHTMLURL = bundle.url(forResource: "Tutorial_main", withExtension: "html") else {
print("No main HTML!")
return nil
}
// The contents of Tutorial_main is not just a String, it's a format (see below)
guard let mainHTMLFormat = try? String(contentsOf: mainHTMLURL, encoding: .utf8) else {
print("Failed to parse \(mainHTMLURL.lastPathComponent)!")
return nil
}
guard let bodyURL = bundle.url(forResource: bodyFileName, withExtension: "html") else {
print("No body with name \(bodyFileName)!")
return nil
}
guard let htmlBody = try? String(contentsOf: bodyURL, encoding: .utf8) else {
debugLog("Failed to parse \(bodyFileName)!")
return nil
}
// Prepare some necessary arguments...
let arguments = [
// Following values will be used as CSS constants
UIColor.lightGray.hexString, // softTextColor
UIColor.gray.hexString, // darkTextColor
UIColor.green.hexString, // actionColor
UIColor.white.hexString, // backgroundColor
"15px", // bodyFontSize
"22px", // titleFontSize
htmlBody // the body, yes
]
// And here we ASSEMBLE! Finally.
let assembledHTMLString = withVaList(arguments) {
// The `arguments` substitute %@ in the `format`, one by one.
(NSString(format: mainHTMLFormat, arguments: $0)) as String
}
return assembledHTMLString
}</code>
Wondering where is <code class="prettyprint lang-swift">hexString</code> property of <code class="prettyprint lang-swift">UIColor</code> coming from? – Read about the <a href="https://kelindev.blogspot.com/2018/02/power-of-extensions.html">Power of Extensions</a>.<br /><br />
<h4>Configure WKWebView</h4>
You can configure it on your own. But if you don't know how to disable zooming, here is some help:
<code class="prettyprint lang-swift block">webView.scrollView.delegate = self
extension TutorialViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return nil // Just return nil here
}
}</code>
When you have the assembled HTML string, there is not much left to do:
<code class="prettyprint lang-swift block">webView.loadHTMLString(assembledHTMLString,
baseURL: Bundle.main.bundleURL)</code><br />
This all may seem overcomplicated to you, but once you implement the logic, you will have a very useful tool.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-4232745147632063782018-05-26T07:25:00.002+01:002018-08-13T16:06:28.586+01:00How do I highlight code in the blog<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
May be you are a developer who has plenty of ideas to share, and you want to start your own blog? In this case you need a <b>code highlighting</b> first!<br /><br />
How do I highlight code here? Well, first of all I import <a href="https://github.com/google/code-prettify">Google code-prettify</a> script, basically every post in this blog starts with the line:
<code class="prettyprint lang-html block"><script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script></code><br />
To add some Swift code, like this: <code class="prettyprint lang-swift">let reality = true</code>, I use the <code>code</code> tag:
<code class="prettyprint lang-html block"><code class="prettyprint lang-swift">let reality = true</code></code>
Here <code>prettyprint</code> feeds our code block to the prettifier and <code>lang-swift</code> is a language hint. In <a href="https://stackoverflow.com/a/36880075/3050403">this Stackoverflow answer</a> you can find a list of possible hints.<br /><br />
I apply custom CSS to the <code>code</code> block. In Blogger you can do it following the next path: Theme > Customize > Advanced > Add CSS.
<code class="prettyprint lang-css block">code {
white-space: pre-wrap; // Required to keep formatting, still can wrap long lines
font-size: 15px;
line-height: 100%;
background-color: #f8f7f1;
padding: 4px 2px 4px 1px;
}</code>
Additionally, to create nice code blocks like above, I use the <code>block</code> class:
<code class="prettyprint lang-css block">code.block {
display: block;
padding: 0.4em 0.15em 0.4em 0.15em;
margin: 0.4em 0 0.4em 0;
}</code>Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-1960496197952951802018-05-22T22:24:00.000+01:002018-05-25T21:29:23.849+01:00Swift guidelines to follow<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
I started using <a href="https://developer.apple.com/swift/">Swift</a> in <a href="https://itunes.apple.com/us/developer/vladimir-kelin/id933241655">my projects</a> in 2015 and, as any Swift newbie, I was surprised by the lack of code style guidelines. Here I will share my experience of 3 years and point guidelines that worth following in different aspects of development.<br /><br />
<a href="https://swift.org/documentation/api-design-guidelines/#naming">Apple Swift API Design Guidelines</a> provide great rules for <b>naming</b>. I also recommend to use Objective-C style prefixes for public <a href="https://kelindev.blogspot.com/2018/02/power-of-extensions.html">extensions</a> to avoid possible conflicts:
<code class="prettyprint lang-swift block">/// MARK: Module XXX
extension UIImageView {
func xxx_loadImageFromURL(_ url: URL) {
// ...
}
}
// ...
customImageView.xxx_loadImageFromURL(url)</code><br />
Built-in Xcode formatter (<code>^I</code>) closes most questions with <b>indentation</b>. Just accept, that sometimes it can produce strange results, like this:<br />
<code class="prettyprint lang-swift block"> URLSession.shared.dataTask(with: apiURL) { (data, _, err) in
}.resume()</code>
instead of this:<br />
<code class="prettyprint lang-swift block"> URLSession.shared.dataTask(with: apiURL) { (data, _, err) in
}.resume()</code><br />
Considering newlines, I recommend to follow good old <a href="https://google.github.io/styleguide/objcguide.html">Google Objective-C Style Guide</a>, which is stating: <i>"Use vertical whitespace sparingly."</i><br /><br />
<b>All the rest</b> is covered by <a href="https://github.com/raywenderlich/swift-style-guide">The Official raywenderlich.com Guide</a>. This guide is consistent and detailed, yet simple. It's widely adopted in different projects and tutorials, so it's well tested and recognizable. Note, that the guide is optimized for tutorials and sample code, which is good, because it's easy to read. But you can ignore some rules, like Minimal Imports or Use Type Inferred Context, because they don't improve reading but can slow down the writer.<br /><br />
While writing this post I found another style guide from <a href="https://github.com/linkedin/swift-style-guide">LinkedIn</a>. I don't recommend to follow it. Some rules listed there are obvious, some duplicate Xcode warnings, and some rules are disputable. The guide is way too strict, too large to remember, and thus it will be hard to adopt in your team.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-53393585725895528392018-02-20T10:06:00.000+00:002018-06-04T19:11:21.929+01:00Power of Extensions<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<p class="note">Article is valid for Swift 4.0.<br /></p>
In this article I want to share my practical and emotional experience of using Swift extensions. I’m really impressed how powerful they become in comparison with old Objective-C <i>categories</i>.<br /><br />
To create a category in <b>Objective-C</b> you must create 2 files (.h and .m) and support both private and public interfaces. Categories are unsafe: compiler allows you to redefine any method in a class/subclass/superclass and according to <a href="https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html">Docs</a> (Avoid Category Method Name Clashes paragraph) the behavior is undefined. Actually, it depends on the order of files in Compile Sources list. Also, the one moment is not so obvious, but very important: each Objective-C category should have a name. Why that's an issue? – you may ask. – Because the name usually corresponds to the category features so for each new feature you need a new category, that means 2 files. Such operations eat time, thats why.<br /><br />
Now compare it to <b>Swift</b>. Compiled extensions in swift are much safer and predictable. Try to redefine a method or variable – you will get an `Invalid redeclaration` compiler error. Swift <code class="prettyprint lang-swift">extension</code> doesn't need a name and to create it you usually don't need a separate file. Thanks to this I'm shooting extensions like from a machine gun.<br />
<p class="note">Interesting, that Objective-C also has its own extensions which are just categories without a name. But such anonymous categories can be defined only in the .m file and therefore are limited.<br /></p>
Here I put some common <code class="prettyprint lang-swift">extension</code> applications:<br />
<ol>
<li><b>Code grouping.</b> Implementing protocols in the separate blocks:
<code class="prettyprint lang-swift block">// MARK: UITableViewDataSource
extension ScheduleViewController: UITableViewDataSource {
// ...
}</code>
(Though protocol methods implemented in extensions couldn’t be overridden yet.)<br /><br />
Thematic or logical scopes:<br />
<code class="prettyprint lang-swift block">// MARK: - PRIVATE
private extension AppDelegate {
func makeRootViewController() -> UIViewController {
// ...
}
}</code><br />
</li>
<li><b>New way to define constants:</b><br />
<code class="prettyprint lang-swift block">extension Int {
static let maxLocalNotificationsCount = 64
}
extension TimeInterval {
static let secondsInMinute: TimeInterval = 60
static let secondsInHour: TimeInterval = 60 * .secondsInMinute
}</code><br />
Usage: <code class="prettyprint lang-swift">request.timeoutInterval = 2 * .secondsInMinute</code><br /><br />
</li>
<li><b>Default implementation.</b> A powerful feature since Swift 3.
<code class="prettyprint lang-swift block">protocol OffsetListParserProtocol: class {
// ...
func loadNext(completion: LoadHandler?)
}
extension OffsetListParserProtocol {
func loadNext(completion: LoadHandler?) {
guard !isFinished && !isLoading else {
completion?(nil, nil)
return
}
// Start loading...
}
}</code><br />
BTW, it's the only way to create a common implementation for <code class="prettyprint lang-swift">struct</code>'s because they cannot inherit.<br /><br />
</li>
<li>And of course <b>extending</b> the abilities of existing system, third party or your own types:
<code class="prettyprint lang-swift block">public extension UIViewController {
func firstAncestorOfType<T>(_ type: T.Type) -> T? where T: UIViewController {
guard let someParent = parent else { return nil }
if let requiredParent = someParent as? T {
return requiredParent
} else {
return someParent.firstAncestorOfType(type)
}
}
}</code>
</li>
</ol><br />
Handy extensions that we create from time to time can be aggregated in git submodules or Swift frameworks, allowing to share improvements among a whole bunch of projects, greatly rising the speed of development. Instead of copypasting the code snippets you just create extensions (less often types or global functions). Like this: <br />
<code class="prettyprint lang-swift block">// Example from my SwiftEssentials framework
public extension UIColor {
var hexString: String {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: nil)
let intR = Int(round(r * 255))
let intG = Int(round(g * 255))
let intB = Int(round(b * 255))
return String(format: "#%02lX%02lX%02lX", intR, intG, intB)
}
convenience init(Hº: CGFloat, S%: CGFloat, B%: CGFloat, A: CGFloat) {
self.init(hue: Hº / 360, saturation: S% / 100, brightness: B% / 100, alpha: A)
}
convenience init(hex: UInt32, alpha: CGFloat) {
self.init(
red: CGFloat((hex & 0xFF0000) >> 16) / 255,
green: CGFloat((hex & 0x00FF00) >> 8) / 255,
blue: CGFloat(hex & 0x0000FF) / 255,
alpha: alpha
)
}
}</code><br />
I’m very excited about new possibilities that Swift provides. I have learned Swift since the origin, see how it grows and I don't stop to notice how fast, convenient and productive this language is. Due to <code class="prettyprint lang-swift">extension</code>'s I don’t bother if I see any imperfections in the system libraries anymore. I just fix them and forget :)<br />
<code class="prettyprint lang-swift block">public extension UIView {
func removeSubviews() {
for subview in subviews {
subview.removeFromSuperview()
}
}
}</code>Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-90161781116961755372018-02-09T21:32:00.000+00:002018-02-09T21:32:57.687+00:00There are Two Types of Software Design...<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmZ1ViUR_COqPUSy-5XZMjRbJy95peLW-V_cHgZS3_dE-MJZgV7qlQCXUxpMpx1zimcX55cF0ULPCU9MrXxrsD3f_EFtxdVq6FiM-1IBKGu64EuOezUDdgo_K35_ifJTo6gko1PEQV2dY/s1600/There+are+Two+Types+of+Software+Design+%25D0%25BA%25D0%25BE%25D0%25BF%25D0%25B8%25D1%258F.png" data-original-width="1000" data-original-height="642" /></div>Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-80347257934937554452018-02-03T01:51:00.002+00:002018-02-03T07:40:59.465+00:00Changing view's type on the Storyboard<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1D69HMJ6zr0Z357HF_Cf0HGEApRON-FW5i7Kx3BF8dmuwzUKsaBYYkXXD4VNAIG93OZ6D35zEXxfO_eHlcMVQezleYmdP6oACwb_NMW7X_g0EQb-VK5ghgKQIvYxCrDNKaGgJb43T9Us/s1600/Before-After.png" data-original-width="651" data-original-height="539" /></div><br />
Suppose the design of your app changed, and now you need to replace the <b>Table View</b> with a <b>Collection View</b> keeping all the constraints along with views hierarchy.<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuzj4sT4mEGDyhjYiyfghWppin58Ut4QRO9lHW40jkyw-SnU35pRIF-wA-fy8416saretb8QyVUomPVwUXvuIEPEcHvl7GW5QIS7tFTW6GdebHj_WMmSLODYyE7nEgp1jqGKd2_oozUgg/s1600/CollectionView.png" data-original-width="428" data-original-height="806" /></div>
Drop a new View Controller on the storyboard and put a Collection View inside. Rename it in the left panel (called Document Outline). Name it "The Replacement" so you will be able recognize it later. Now switch to the <b>Source Code</b> through the <b>Open As</b>.
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFAivbgFnISvBlEoWTp1Q_JmH4hS9AKechBpwwxfd8zp6sWioaEx5Kuow4ZeH0EKdGJbKPCRcOQdMnlPAsxr8N0b3t57ZPhObhouvNPt9MMhXqAgmoS6hw3dLYvQ4DPi1JFOSf9YL6a4A/s1600/OpenAs.png" data-original-width="882" data-original-height="245" /></div>
Now the storyboard looks like XML markup. Find the Table View you need to replace, it will be scoped within the <b>tableView</b> tag.
<br /><br /><div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwRBoO3aWoJHdtsukD6OR68NYXvaQlKmH3N_mujdfVdvYdp8Hhq-KJ9sU57hTYeszUKIS2grk1JmMIWvjO45q493JEty7NXiV0ujhilDCAYoDa5dMcjokNEXRxPwjZd1QvTkvNUoOsUuc/s1600/TableId.png" data-original-width="860" data-original-height="169" /></div><br />
Copy its <b>id</b> (example: id="8Oa-xL-ums"). It points to the view in constraints and outlets. Now find the new Collection View under the <b>collectionView</b> tag.
<br /><br /><div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_RZm77lmRyOqmr0q9M_zh4mNet2_-TotPdqgMmRPOoO4M9drPaM7c_lFosGdO7_x3BozUZUCJZKbkxDmZHBCu7W77NlRJSOPK5cE4sEzQ5r7wkzxX-8OjrqIamtCag6z8D0LW9JAtRdc/s1600/CollectionViewUserLabel.png" data-original-width="875" data-original-height="189" /></div><br />
Copy everything including the enclosing tags: <code><collectionView ...</collectionView></code> and paste it instead of the <b>tableView</b>. Replace Collection View's <b>id</b> with the value you copied from the Table View. Before switching back to Interface Builder you need to clean up the storyboard. Remove the scene of the temporary View Controller that you created at the beginning of the tutorial. Delete everything between tags and the preceding comment: <code><!--View Controller></code><code><scene ...</scene></code>. If you don't do this you will have an error because of duplicated id.
<br /><br /><div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrP2xALopdaLTnOTMxPx8jZ32hapWWZfi6ukkCwYjJ-Yvpyd403EyQbxvraLB0IXrwTU1-Sg2sWRLEsot934QyZx6RAwAeEeERjhlF8gWiK1XgdhSMLu9oUhs65G8_G9f4zZHxRGdb5AQ/s1600/Scene.png" data-original-width="900" data-original-height="146" /></div><br />
Now you will have the new Collection View constrained like the Table View but misplaced, because it's was copied with the rect.
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNdlhdmP5wyQhRVoUaY1Nn3nINT5pvyaoZfteip-FYZrEabhTZYDVrAgqyhZGwL9rEYYsKVfkreoX1JueHXSwrUGNwy3mtIBU-HqFhet6UHBNSKRPpzNWhDaNI_fLCLJj2iFO2w_wZkJk/s1600/MisplacedReplacement.png" data-original-width="429" data-original-height="806" /></div>
Click Update Frames and enjoy the result!<br /><br />
Here is the list of storyboard tags for different UI classes. Who knows, may be it will be useful.<br /><br />
<table>
<tr><td class="right">arscnView</td> <td class="left">ARSCNView</td></tr>
<tr><td class="right">arskView</td> <td class="left">ARSKView</td></tr>
<tr><td class="right">glkView</td> <td class="left">GLKView</td></tr>
<tr><td class="right">mapView</td> <td class="left">MKMapView</td></tr>
<tr><td class="right">mtkView</td> <td class="left">MTKView</td></tr>
<tr><td class="right">sceneKitView</td> <td class="left">SCNView</td></tr>
<tr><td class="right">skView</td> <td class="left">SKView</td></tr>
<tr><td class="right">activityIndicatorView</td> <td class="left">UIActivityIndicatorView</td></tr>
<tr><td class="right">button</td> <td class="left">UIButton</td></tr>
<tr><td class="right">collectionView</td> <td class="left">UICollectionView</td></tr>
<tr><td class="right">datePicker</td> <td class="left">UIDatePicker</td></tr>
<tr><td class="right">imageView</td> <td class="left">UIImageView</td></tr>
<tr><td class="right">label</td> <td class="left">UILabel</td></tr>
<tr><td class="right">navigationBar</td> <td class="left">UINavigationBar</td></tr>
<tr><td class="right">pageControl</td> <td class="left">UIPageControl</td></tr>
<tr><td class="right">pickerView</td> <td class="left">UIPickerView</td></tr>
<tr><td class="right">progressView</td> <td class="left">UIProgressView</td></tr>
<tr><td class="right">scrollView</td> <td class="left">UIScrollView</td></tr>
<tr><td class="right">searchBar</td> <td class="left">UISearchBar</td></tr>
<tr><td class="right">segmentedControl</td> <td class="left">UISegmentedControl</td></tr>
<tr><td class="right">slider</td> <td class="left">UISlider</td></tr>
<tr><td class="right">stackView</td> <td class="left">UIStackView</td></tr>
<tr><td class="right">stepper</td> <td class="left">UIStepper</td></tr>
<tr><td class="right">switch</td> <td class="left">UISwitch</td></tr>
<tr><td class="right">tabBar</td> <td class="left">UITabBar</td></tr>
<tr><td class="right">tableView</td> <td class="left">UITableView</td></tr>
<tr><td class="right">textField</td> <td class="left">UITextField</td></tr>
<tr><td class="right">textView</td> <td class="left">UITextView</td></tr>
<tr><td class="right">toolbar</td> <td class="left">UIToolbar</td></tr>
<tr><td class="right">view</td> <td class="left">UIView</td></tr>
<tr><td class="right">containerView</td> <td class="left">UIView (with embedded View Controller)</td></tr>
<tr><td class="right">visualEffectView</td> <td class="left">UIVisualEffectView</td></tr>
<tr><td class="right">webView</td> <td class="left">UIWebView</td></tr>
<tr><td class="right">wkWebView</td> <td class="left">WKWebView</td></tr>
</table>
<style type="text/css">
td.right {
text-align: right;
}
td.left {
text-align: left;
}
table {
border-spacing: 1em 0.4em;
font-size: 16px;
font-weight: normal;
}
</style>Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-88180416622020700082018-01-27T22:40:00.000+00:002019-02-20T21:45:36.163+00:00Catching nil as Error<a href="https://gist.github.com/kvaDrug/8b89443941a6380ffa1074035013ec0f">Github gist.</a><br /><br />
I've posted this as an <a href="https://stackoverflow.com/a/42696116/3050403">answer on stackoverflow</a> before, and the information was highly appreciated by some people. The question was like: “How to catch any error, specially unexpectedly found nil in Swift?" Though the author was asking about catching system errors, I decided to post my super-handy Unwrap Snippet there.</br>
<code class="prettyprint lang-swift block">struct UnwrapError<T>: Error, CustomStringConvertible {
let optional: T?
public var description: String {
return "Found nil while unwrapping \(String(describing: optional))!"
}
}
public func unwrap<T>(_ optional: T?) throws -> T {
if let real = optional {
return real
} else {
throw UnwrapError(optional: optional)
}
}</code><br />
The idea of this approach is to replace multiple <code class="prettyprint lang-swift">if let</code> and <code class="prettyprint lang-swift">guard let</code> statements with a single <code class="prettyprint lang-swift">do-try-catch</code> block. You can:</br>
<code class="prettyprint lang-swift block">do {
// Parse JSON and assign variables which were defined somewhere above
let dictionary = try unwrap(JSONSerialization.jsonObject(
with: data,
options: .allowFragments) as? [String: Any])
isAdsEnabled = try unwrap(dictionary["isAdsEnabled"] as? Bool)
// While calling function which require a non-optional parameter in one line of code
imageView.image = try UIImage(data: unwrap(receivedData))
// And also you can simplify the building of multipart data
var data = Data()
data += try unwrap(dispositionString.data(using: .utf8))
data += someContent
data += try unwrap("\r\n".data(using: .utf8))
} catch error {
// Handle error
// ...
// and exit
return nil
}</code><br />
Those who make apps with web backend will definitely like.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com1tag:blogger.com,1999:blog-4999718079120215268.post-89670027188677380532018-01-19T10:38:00.000+00:002019-02-20T21:47:47.396+00:00Safer parsing with JSONSerialization in Swift<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<a href="https://gist.github.com/kvaDrug/fe71fe09a9e301a11e9c02903777220d">Github gist.</a><br /><br />
Most people use the following snippet to get a value from an object provided by the <a href="https://developer.apple.com/documentation/foundation/jsonserialization">JSONSerialization</a>:<br />
<code class="prettyprint lang-swift block"> guard let name = jsonDictionary["name"] as? String else { return }
...</code><br />
It's not obvious, but this code is unsafe. It can easily fail if the "name" value will be "111". In this case it may be decoded as <code>NSNumber</code> and casting to <code>String</code> will always fail. The same thing with numbers:<br />
<code class="prettyprint lang-swift block"> guard let id = jsonDictionary["id"] as? Int else { return }
// id can be a "3" String!
...</code>
A value like "3" can be decoded both as <code>NSString</code> and <code>NSNumber</code> (more often).<br /><br />
I didn't find any tutorial that would take this issue into account. All of them recommend to use <code>as?</code> to unknown JSON object to basic type. So I post here the <b>correct</b> snippets that will provide stable and predictable results in all possible cases.<br />
<h3>JSON helpers</h3>
<code class="prettyprint lang-swift block">extension String {
init?(jsonObject: Any?) {
guard jsonObject is NSNull == false else { return nil }
let aNSObject = jsonObject as? NSObject
if let description = aNSObject?.description {
self = description
} else {
return nil
}
}
}
extension Int {
var boolValue: Bool { return self != 0 }
init?(jsonObject: Any?) {
if let number = jsonObject as? NSNumber {
self = number.intValue
} else if let string = jsonObject as? NSString {
self = string.integerValue
} else {
return nil
}
}
}
extension Bool {
init?(jsonObject: Any?) {
if let integer = Int(jsonObject: jsonObject) {
self = integer.boolValue
} else {
return nil
}
}
}
extension Double {
init?(jsonObject: Any?) {
if let number = jsonObject as? NSNumber {
self = number.doubleValue
} else if let string = jsonObject as? NSString {
self = string.doubleValue
} else {
return nil
}
}
}
</code><br />
All objects produced by <code>JSONSerialization</code> are instances of <code>NSString</code>, <code>NSNumber</code>, <code>NSArray</code>, <code>NSDictionary</code>, or <code>NSNull</code>.Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0tag:blogger.com,1999:blog-4999718079120215268.post-14935957332811357952017-03-28T18:26:00.001+01:002018-06-04T19:37:29.846+01:00How to Install Ringtone on iOSThis detailed guide is intended to cover the whole process of ringtone installation on any iOS device, including iPhone, iPad and iPod. It's constantly updated to correspond to the latest versions of iOS, macOS and other related software, such as iTunes.<br />
<h3>Navigation:</h3>
<a href="#step1">Step 1. Prerequisites</a><br />
<a href="#step2">Step 2. Connection</a><br />
<a href="#step3">Step 3. Import ringtones to iTunes</a><br />
<a href="#step4">Step 4. Open Tones synchronization</a><br />
<a href="#step5">Step 5. Sync</a><br />
<a href="#step6">Step 6. Sound Settings</a><br />
<a href="#step7">Step 7. Set the ringtone</a><br />
<a href="#myApps">Apps by me</a>
<br />
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step1"></a>
Step 1. Prerequisites</h3>
To install a ringtone you will need:<br />
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQN_w4K1l1m-d9rCZKEvspAbcl8pSYChDy4oyZa83O52b3wcoyHNYvSd0BPCvqLXzaHneSKJeE-9LcfjTXfoalkqrL8SsS8ZJJEbWPfJbG1tlGSrTScpr0K-LMM7x9pwyGMRb_EFiC2G8/s320/step1_blog.png" width="320" height="91" /></div>
<ul>
<li><b>iTunes</b> — a computer with iTunes on it.</li>
<li><b>Lightning - USB</b> — to connect the device to a USB port of the computer.</li>
<li><b>.m4r</b> — a ringtone file on your computer with .m4r extension.</li>
</ul>
<div class="moreDetails">
<div class="paragraphDiv">Ringtones can't be longer than <b>40</b> seconds! Other tones (Text Tone, Reminder Alert) are limited to 30 seconds.</div>
<div class="paragraphDiv"><b>.m4r</b> is an extension of MPEG-4 audio just like .m4a, but treated by iTunes as a ringtone. You can create .m4r simply by renaming an .m4a file. When you haven't .m4a file, you can convert it from any other audio format or download a new ringtone with the appropriate extension.</div>
<div class="paragraphDiv">Older devices require Apple 30-pin to USB Cable instead of Lightning.</div></div>
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step2"></a>
Step 2. Connection</h3>
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjE8o-27hf7Hhx7uhJNaHs_8VBdHszUbaEtSyadPLecrbWBUNA8V5ba4HJrfHSebzK9lpUHmuzpcHE0BnwaBHEb8GQOMNuDEIKTAnXihYyMOTlAfcoBfrSrpuV2HHgTWoAomZw5I9PgoFc/s320/step2_blog.png" width="200" height="200" /></div>
Connect your device to your computer using the USB cable.<br />
<div class="moreDetails">
You can sync the device with iTunes using Wi-Fi. In this case you will need the USB cable only once: to activate wireless syncing. <a target="_blank" href="https://support.apple.com/en-us/HT203075">Read more.</a></div>
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step3"></a>
Step 3. Import ringtones to iTunes</h3>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/udbeZextpHQ/0.jpg" frameborder="0" height="360" src="https://www.youtube.com/embed/udbeZextpHQ?feature=player_embedded" width="640"></iframe></div>
<ol>
<li>Open iTunes. Make sure that you have the latest version.</li>
<li>Open your ringtones in the Finder.</li>
<li>Drag and drop them to iTunes.</li>
</ol>
<div class="moreDetails">
<div class="paragraphDiv">If you can't drop an .m4r file to iTunes, then try to select a different category in the top panel, for example Tones. In Tones category you can check ringtones duration or edit the name and other metadata.</div>
<div class="paragraphDiv">Another way to import ringtones is to double-click them.</div></div>
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step4"></a>
Step 4. Open Tones synchronization</h3>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/tZppPrEF09g/0.jpg" frameborder="0" height="360" src="https://www.youtube.com/embed/tZppPrEF09g?feature=player_embedded" width="640"></iframe></div>
<ol>
<li>Click the device icon.</li>
<li>Click Tones in left panel.</li>
</ol>
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step5"></a>
Step 5. Sync</h3>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/HWb51FoGYA8/0.jpg" frameborder="0" height="360" src="https://www.youtube.com/embed/HWb51FoGYA8?feature=player_embedded" width="640"></iframe></div>
<ol>
<li>In the Tones section, check Sync Tones > Selected tones.</li>
<li>In the appearing Tones list, select the tones you want to install to your device.</li>
<li>Click Apply.</li>
</ol>
After syncing will occur, the tones will be copied to the device.<br />
<div class="moreDetails">
<div class="paragraphDiv">Custom tones which aren't included in the list will be removed from the device!</div>
<div class="paragraphDiv">You also can select to sync All tones for simplicity.</div></div>
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step6"></a>
Step 6. Sound Settings</h3>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/mIdbNcYTYyQ/0.jpg" frameborder="0" height="360" src="https://www.youtube.com/embed/mIdbNcYTYyQ?feature=player_embedded" width="640"></iframe></div>
<ol>
<li>Open Settings > Sounds.</li>
<li>Scroll to SOUNDS AND VIBRATION PATTERNS.</li>
</ol>
<div class="moreDetails">
<div class="paragraphDiv">
You also can set your tone to the alarm clock. Open the Clock app, go to Alarm > Edit. Select the alarm, touch Sound and select the imported ringtone from the list.</div>
<div class="paragraphDiv">To setup sound for the specific contact open Contacts, and select the contact. Then touch Edit > Ringtone.</div></div>
<h3>
<a href="http://kelindev.blogspot.com/2017/03/how-to-install-ringtone-on-ios.html" name="step7"></a>
Step 7. Set the ringtone</h3>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/iXKBmfBwHhE/0.jpg" frameborder="0" height="360" src="https://www.youtube.com/embed/iXKBmfBwHhE?feature=player_embedded" width="640"></iframe></div>
<ol>
<li>Go to Ringtone.</li>
<li>Select your tone in the RINGTONES list.</li>
</ol>
You're done! You also can set other sounds (for example Text Tone) in this way.<br />
<div class="moreDetails">
<div class="paragraphDiv">To stop playing the selected sound, touch it again.</div>
<div class="paragraphDiv">Music created with <a target="_blank" href="https://itunes.apple.com/app/garageband/id408709785?mt=8">Garageband</a> can be exported as a ringtone right on the device, bypassing iTunes.</div></div>
<div style="text-align: center; margin: 2em 0 2em 0">*</div>
If you have questions or suggestions, write a comment. If you found this guide useful, please share it with a friend!<br /><br />
<a name="myApps"></a>
You also may may have a look at the apps created by me:
<div style="text-align: center; margin: 3em 0 3em 0"><a style="font-size: 200%" target="_blank" href="https://itunes.apple.com/us/developer/vladimir-kelin/id933241655"><img style="vertical-align: middle; display: inline-block; margin-right: 17px" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfJzphT9nRvvAMalcHM5u88ewtUbemVk8_ue5ACs6CekrZgpFXe4cUV8RkI8CKFGWJphYJWzyYtGhx115PelhXlyEMRL1DKD0xUcXugc0VIw9O5rk7GhawT2x9vzJiHidCuHk6o2ijzcw/s1600/apps_by_me.png" /><b>My apps on the App Store</b></div>
<a href="#">back to top</a>
<style type="text/css">
div.moreDetails {
background-color: #fafafa;
color: #4d4d4d;
padding: 0.5em 1em 0.5em 1em;
margin: 0.5em 0 0.5em 0;
font-size: 83%;
line-height: 150%;
}
.paragraphDiv {
margin-top: 0.47em;
margin-bottom: 0.47em;
}
</style>Vladimir Kelinhttp://www.blogger.com/profile/16245922246789694977noreply@blogger.com0