Showing posts with label how to. Show all posts
Showing posts with label how to. Show all posts

Sunday, October 28, 2018

How to get old WWDC videos

It appears that old WWDC videos are removed from the official list. This post tells how to get them.
So, if you visit the developers's site from the above link you will see that currently available year range is only 2015–2018:
Even the search yields no results:
I had such a problem trying to find 8 year old video (WWDC 2010, Core Animation in Practice). Thanks to reddit community help, several solutions were found:
  1. WWDC videos prior to 2012 can be found in Developer Video Archive.
  2. From 2012 to 2015 they can be accessed by the link: https://developer.apple.com/videos/wwdc2012/. Just change the year in the end.
  3. The keynotes can be found in Podcasts.
Thats all!

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.

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:

Tutorial_main.html

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.

Tutorial.css

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.

step2_body.html

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

Assemble

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 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 } 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="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></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; }

Saturday, February 3, 2018

Changing view's type on the Storyboard


Suppose the design of your app changed, and now you need to replace the Table View with a Collection View keeping all the constraints along with views hierarchy.
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 Source Code through the Open As.
Now the storyboard looks like XML markup. Find the Table View you need to replace, it will be scoped within the tableView tag.


Copy its id (example: id="8Oa-xL-ums"). It points to the view in constraints and outlets. Now find the new Collection View under the collectionView tag.


Copy everything including the enclosing tags: <collectionView ...</collectionView> and paste it instead of the tableView. Replace Collection View's id 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: <!--View Controller><scene ...</scene>. If you don't do this you will have an error because of duplicated id.


Now you will have the new Collection View constrained like the Table View but misplaced, because it's was copied with the rect.
Click Update Frames and enjoy the result!

Here is the list of storyboard tags for different UI classes. Who knows, may be it will be useful.

arscnView ARSCNView
arskView ARSKView
glkView GLKView
mapView MKMapView
mtkView MTKView
sceneKitView SCNView
skView SKView
activityIndicatorView UIActivityIndicatorView
button UIButton
collectionView UICollectionView
datePicker UIDatePicker
imageView UIImageView
label UILabel
navigationBar UINavigationBar
pageControl UIPageControl
pickerView UIPickerView
progressView UIProgressView
scrollView UIScrollView
searchBar UISearchBar
segmentedControl UISegmentedControl
slider UISlider
stackView UIStackView
stepper UIStepper
switch UISwitch
tabBar UITabBar
tableView UITableView
textField UITextField
textView UITextView
toolbar UIToolbar
view UIView
containerView UIView (with embedded View Controller)
visualEffectView UIVisualEffectView
webView UIWebView
wkWebView WKWebView

Saturday, January 27, 2018

Catching nil as Error

Github gist.

I've posted this as an answer on stackoverflow 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.
struct UnwrapError: Error, CustomStringConvertible { let optional: T? public var description: String { return "Found nil while unwrapping \(String(describing: optional))!" } } public func unwrap(_ optional: T?) throws -> T { if let real = optional { return real } else { throw UnwrapError(optional: optional) } }
The idea of this approach is to replace multiple if let and guard let statements with a single do-try-catch block. You can:
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 }
Those who make apps with web backend will definitely like.

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.

Navigation:

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.
  2. Scroll to SOUNDS AND VIBRATION PATTERNS.
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