Showing posts with label guide. Show all posts
Showing posts with label guide. Show all posts

Tuesday, August 14, 2018

Sorting and Choosing Keywords for App Store

Updated: 12 Mar 2019.

Suppose you already have plenty keyword ideas for your app. Or, at least, you understand how to select them.

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.

Organize Keywords in a Spreadsheet

Additionally, suppose that the target app is named Plant Classifier and it's a visual recognition deep machine learning neural network startup based app, allowing users to identify a plant by it's photo. Here's its icon:
(it's the kenaf and not what you think)

Your keyword ideas are: 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. 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.

You think the app page with such keywords will easily match searches like:
  • plant species
  • identify tree genus
  • or determine flower by picture

Now do the first simple yet important step: create a spreadsheet. Make a column named Keywords and paste all 26 ideas there.
The spreadsheet will allow you to store keywords data in the convenient way and finally sort it. I prefer Google Sheets but any other tool will meet our demands.

Analyse App Store Search Results

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 concurrency. The most competitive words will evoke more search results. Luckily we don't need to do this manually, because we have Keywording utility based on iTunes Search API.

For example, the request:
./keywording "taxonomy,species" NZ will give: NZ species 164 taxonomy 26
Create a column: Concurrency, App Store results. Set the country to more popular US store and perform search for all the words, filling the newly created column:

Estimate Search Volume

To estimate the search volume we will use Google Ads Keyword Planner. 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:
  1. Select "Get search volume and forecasts" box.
  2. Paste keywords there, and click GET STARTED.
  3. You will see the results under FORECAST section.
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 photo, picture and image have enormously heightened clicks.

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".

Now set English language and write Impressions to the spreadsheet under Impressions, Google column. You will get something like this:

Determine Linguistic Frequency

Linguistic frequency 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.

But in the educational purpose you will determine the linguistic frequency of each keyword using Ngram (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:
and after spending some time while putting them into the Ngram, frequency column of the table, you will get:

Sorting and Choosing

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:
Noticed the words with low concurrency but high search volume? They are "golden", you choose them, definitely. First five are highlighted with yellow: garden, tree, leaf, name, herb. Their total length is only 26. You need sufficiently more.

If we select all words with low competition, we will obtain the following: garden, tree, leaf, name, herb, analysis, shrub, recognition, herbarium, botany, visual, taxonomy, genus, species, identify, nomenclature, determine, systematics (total length - 144). Some words have to be dropped.

Now sort the table by Concurrency, increasing. What do you see there?
Some quite unpopular, low-concurrent yet meaningful in the context of your app words, like: herbarium, taxonomy, botany, shrub, they are silver words (blue). If you need to drop some words, you better leave them.

Another interesting group is so-called red words which are popular, highly concurrent but closely describe your app. Such as flower, image, picture, photo. Consider them if you need to add some words to the final selection.

So, suppose, you drop analysis, nomenclature, systematics, determine (not very suitable, anyway). Now we you have exactly 100 characters: garden, tree, leaf, name, herb, shrub, recognition, herbarium, botany, visual, taxonomy, genus, species, identify. So neat!

Other Considerations

But could it be neater? There is still a place for the art in the keyword optimisation.

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 recognition with recognize 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.

Choose not only keywords, but their combinations. For example garden keyword is very sweet according to our research. But, how folks supposed to use it? Make some lame request like app for garden? You shouldn't rely on that. Instead, take at least one of "red" image-related words, let's say ... image.

BTW, you probably already noticed that weed 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+.

In conclusion, we've got 14 most efficient keywords of total length 97 characters without whitespaces: image, tree, leaf, name, herb, shrub, recognize, herbarium, botany, visual, taxonomy, genus, species, identify. 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.

The example spreadsheet is available in HTML and Google Sheets.

Saturday, June 30, 2018

Handling background touches: UIControl instead of UITapGestureRecognizer

Usually, to handle background touches on a View Controller we add a UITapGestureRecognizer. The setup requires a few lines of code, but there is a way, you can handle background touches even more easy!

What you need to do is just to change the root view type from UIView to UIControl and attach an action. With Storyboard:
  1. Select the root view.
  2. In the Identity inspector simply change the type to UIControl. (Serious type change isn't required.)
  3. Finally, connect an action to the Touch Down event using Connections inspector.
Then the action can be utilized, for example, to hide a keyboard: @IBAction func backgroundTouched(_ sender: Any) { // Hide keyboard view.endEditing(true) }
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.

Monday, June 4, 2018

How to convert HTML to NSAttributedString?

Well, simply don't. Use WKWebView instead.
This will allow you to insert pictures, reuse CSS-styles, execute scripts etc.

Wait, why not to use NSAttributedString?

While Apple provides several methods to initialize an instance of NSAttributedString from an HTML data, like: init(data:options:documentAttributes:), 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.

You may find many third-party solutions on GitHub (DTCoreText 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.

The only advantage of NSAttributedString is the ability to put it in UI elements like UILabel or UIButton.

WKWebView example

Here follows super-advanced example demonstrating widest possible abilities of displaying rich and interactive text with HTML, CSS, and JavaScript. To make things convenient and reusable the HTML document is split into parts:


The main HTML that contains necessary elements that supposed to be involved in every page of the tutorial. Including:
  • A <head> block containing:
    • A raw CSS <style> section, containing easy-configurable constants.
    • An external CSS "stylesheet".
    • The extremely important "viewport" <meta> tag, required to scale content of a WKWebView correctly.
    • A <script> tag that provides Tutorial.js script. Not important enough to be listed here.
  • A customisable <body> section.
<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> The %@ 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 baseURL to the WKWebView. Yes, you can refer local files, even use anchor links.


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; } It's handy to keep CSS in a separate file. You can see some constants which already were listed before.


<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="">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> The body of the second step. Look at two buttons, there is something interesting about them. First, they use .svg 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.: href="showmore://". These anchors allows to intercept button clicks on the Swift-side using the webView(,decidePolicyFor:,decisionHandler:) method of WKNavigationDelegate.


Time to put all the things together. 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, // 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 } Wondering where is hexString property of UIColor coming from? – Read about the Power of Extensions.

Configure WKWebView

You can configure it on your own. But if you don't know how to disable zooming, here is some help: webView.scrollView.delegate = self extension TutorialViewController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return nil // Just return nil here } } When you have the assembled HTML string, there is not much left to do: webView.loadHTMLString(assembledHTMLString, baseURL: Bundle.main.bundleURL)
This all may seem overcomplicated to you, but once you implement the logic, you will have a very useful tool.

Saturday, May 26, 2018

How do I highlight code in the blog

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 code highlighting first!

How do I highlight code here? Well, first of all I import Google code-prettify script, basically every post in this blog starts with the line: <script src=""></script>
To add some Swift code, like this: let reality = true, I use the code tag: <code class="prettyprint lang-swift">let reality = true</code> Here prettyprint feeds our code block to the prettifier and lang-swift is a language hint. In this Stackoverflow answer you can find a list of possible hints.

I apply custom CSS to the code block. In Blogger you can do it following the next path: Theme > Customize > Advanced > Add CSS. 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; } Additionally, to create nice code blocks like above, I use the block class: code.block { display: block; padding: 0.4em 0.15em 0.4em 0.15em; margin: 0.4em 0 0.4em 0; }

Tuesday, May 22, 2018

Swift guidelines to follow

I started using Swift in my projects 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.

Apple Swift API Design Guidelines provide great rules for naming. I also recommend to use Objective-C style prefixes for public extensions to avoid possible conflicts: /// MARK: Module XXX extension UIImageView { func xxx_loadImageFromURL(_ url: URL) { // ... } } // ... customImageView.xxx_loadImageFromURL(url)
Built-in Xcode formatter (^I) closes most questions with indentation. Just accept, that sometimes it can produce strange results, like this:
URLSession.shared.dataTask(with: apiURL) { (data, _, err) in }.resume() instead of this:
URLSession.shared.dataTask(with: apiURL) { (data, _, err) in }.resume()
Considering newlines, I recommend to follow good old Google Objective-C Style Guide, which is stating: "Use vertical whitespace sparingly."

All the rest is covered by The Official Guide. 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.

While writing this post I found another style guide from LinkedIn. 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.

Tuesday, March 28, 2017

How to Install Ringtone on iOS

This 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.


Step 1. Prerequisites
Step 2. Connection
Step 3. Import ringtones to iTunes
Step 4. Open Tones synchronization
Step 5. Sync
Step 6. Sound Settings
Step 7. Set the ringtone
Apps by me

Step 1. Prerequisites

To install a ringtone you will need:
  • iTunes — a computer with iTunes on it.
  • Lightning - USB — to connect the device to a USB port of the computer.
  • .m4r — a ringtone file on your computer with .m4r extension.
Ringtones can't be longer than 40 seconds! Other tones (Text Tone, Reminder Alert) are limited to 30 seconds.
.m4r 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.
Older devices require Apple 30-pin to USB Cable instead of Lightning.

Step 2. Connection

Connect your device to your computer using the USB cable.
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. Read more.

Step 3. Import ringtones to iTunes

  1. Open iTunes. Make sure that you have the latest version.
  2. Open your ringtones in the Finder.
  3. Drag and drop them to iTunes.
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.
Another way to import ringtones is to double-click them.

Step 4. Open Tones synchronization

  1. Click the device icon.
  2. Click Tones in left panel.

Step 5. Sync

  1. In the Tones section, check Sync Tones > Selected tones.
  2. In the appearing Tones list, select the tones you want to install to your device.
  3. Click Apply.
After syncing will occur, the tones will be copied to the device.
Custom tones which aren't included in the list will be removed from the device!
You also can select to sync All tones for simplicity.

Step 6. Sound Settings

  1. Open Settings > Sounds.
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.
To setup sound for the specific contact open Contacts, and select the contact. Then touch Edit > Ringtone.

Step 7. Set the ringtone

  1. Go to Ringtone.
  2. Select your tone in the RINGTONES list.
You're done! You also can set other sounds (for example Text Tone) in this way.
To stop playing the selected sound, touch it again.
Music created with Garageband can be exported as a ringtone right on the device, bypassing iTunes.
If you have questions or suggestions, write a comment. If you found this guide useful, please share it with a friend!

You also may may have a look at the apps created by me: back to top

How to Record Calls on iPhone