Weekly Swift articles, podcasts and tips by John Sundell.

A first look at the Natural Language framework

Published on 04 Jun 2018
Basics article available: Strings

Welcome to the first Daily WWDC Update - a week-long series of daily articles, in which we'll take a first look at some of the new cool APIs and frameworks that Apple are introducing during this year's WWDC conference.

To kick things off, let's try out an exciting new cross-platform framework called Natural Language. It provides a high-level API enabling easy access to a suite of language detection features when working with text in an app.

What does the API of this new framework look like, and what could it be used for? Let's take it for a spin!

Language detection

Let's say we're building a social networking app and we want to be able to show what language a certain post or comment was written in. Normally, this is something that would require either crude guesswork or sophisticated machine learning models - but now, with Natural Language, it's as easy as asking NLLanguageRecognizer to detect the dominant language in a given string:

let englishText = "Hello, how are you?"
let swedishText = "Hej, hur är läget?"
let spanishText = "¿Hola, como estás?"

let english = NLLanguageRecognizer.dominantLanguage(for: englishText)
let swedish = NLLanguageRecognizer.dominantLanguage(for: swedishText)
let spanish = NLLanguageRecognizer.dominantLanguage(for: spanishText)

The cool thing is that it even seems to work really well for strings that contain multiple languages. For example, here we have a string that contains both English and Swedish, with English being the dominant language - which is correctly detected by NLLanguageRecognizer:

let mixedText = "Jag heter John, and I'm an iOS Developer from Sweden"
let language = NLLanguageRecognizer.dominantLanguage(for: mixedText)
print(language) // NLLanguage.english

Tokenizing

Another feature that Natural Language provides is the ability to tokenize a string into components - or tokens. This could come very much in handy if an app we're working on includes some kind of text composer, and we want to be able to display some quick stats - like the word or sentence count.

Tokenization is done using the NLTokenizer class, which provides a few different units for splitting a string up into tokens - like sentence, word and paragraph. Here we're detecting how many words a given string contains, and then displaying that word count using a label:

let text = "Hello, I'm pretty excited about Natural Language!"

let tokenizer = NLTokenizer(unit: .word)
tokenizer.setLanguage(.english)
tokenizer.string = text

let tokens = tokenizer.tokens(for: text.startIndex..<text.endIndex)
wordCountLabel.text = "You have typed \(tokens.count) words"

Our wordCountLabel will in this case say "You have typed 7 words".

Detecting names of people and places

Perhaps the coolest feature of the new Natural Language framework is how it can easily detect names of people, places and organizations in a string. This could end up being super useful in order to add intelligent predictions and suggestions to many kinds of apps.

Perhaps we're building a messaging app, and we want to be able to detect names of people and match them against the user's contacts in order to make those names interactive? Or perhaps we want to detect names of places and offer to look up directions? There are loads of possibilities here, and the API seems to be both very powerful and easy to customize.

To detect names and places in a string, we'll use the NLTagger class with the nameType scheme. We'll then ask it to detect all tags in a given string, using a set of options that minimize "noisy" tags such as punctuation and whitespace, like this:

let text = "Hello, my name is John Sundell and I live in Kraków"

let tagger = NLTagger(tagSchemes: [.nameType])
tagger.string = text

let tags = tagger.tags(
    in: text.startIndex..<text.endIndex,
    unit: .word,
    scheme: .nameType,
    options: [
        .omitPunctuation,
        .omitWhitespace,
        .omitOther,
        .joinNames
    ]
)

Now to actually pull out any name and place from the returned tags, we'll iterate through them and handle the ones that are for a personalName or a placeName:

var detectedName: String?
var detectedPlace: String?

for (tag, range) in tags {
    switch tag {
    case .personalName?:
        detectedName = String(text[range])
    case .placeName?:
        detectedPlace = String(text[range])
    default:
        break
    }
}

print("Detected name: \(detectedName)")
print("Detected place: \(detectedPlace)")

Which will print "Detected name: John Sundell" and "Detected place: Kraków". Pretty cool! 😀

Conclusion

Natural Language seems like a powerful, yet easy to use, framework for adding natural language features such as tokenization and name, language & place detection to an app. Granted, this is only a first very quick look at this new API, so I'm sure many new ways to use it (as well as bugs and quirks) will be found over the next few months.

You can test this out yourself by dropping the above code samples into a Playground in the Xcode 10 Beta and play around with it.

I hope you enjoyed this first Daily WWDC Update. Like always - your questions, comments and feedback is more than welcome - so let me know what you think of this new format on Twitter @johnsundell.

Thanks for reading! 🚀