Finding Data Patterns in SwiftUI using Natural Language Processing

DevTechie Inc
Jul 9, 2023

Finding Data Patterns in SwiftUI using Natural Language Processing

Machine Learning & Natural Language Processing is deeply integrated in Apple’s ecosystem. Whether we realize or not but all Apple devices rely heavily on Machine Learning.

iOS has many APIs to leverage power of Natural Language Processing in our own apps, one such API is NSDataDetector. This class came into picture in iOS with the iOS 4.0 release.

NSDataDetector uses NLP to match various different kinds of data in text. It can detect time, date, address, links and many more. Today, we will explore this API to find patterns in textual data.

NSDataDetector comes from Objective C world so its API is not as Swifty as other modern APIs are.

Let’s start with an example.

struct DevTechieDataDetectorExample: View {
    @State private var inputString = "DevTechie can be reached at devtechieinc@gmail.com. It's located in 123 main street, San Francisco, California. You can call (555)555-5555 for more info."
    @State private var results = [String]()
    var body: some View {
        NavigationStack {
            VStack {
                TextEditor(text: $inputString)
                    .overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.orange.gradient, lineWidth: 2))
Button("Process") { }
                List(results, id: \.self) { item in
                    Text(item)
                }
            }
            .navigationTitle("DevTechie")
            .padding()
        }
    }
}

Text processing will start with an instance of NSDataDetector. NSDataDetector expects us to pass all the types we are planning to check. In our case, we expects to check for links, address and phone number.

let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue | NSTextCheckingResult.CheckingType.address.rawValue | NSTextCheckingResult.CheckingType.phoneNumber.rawValue)

Next, inside the process button, we will determine the text range so we can pass that into the detector.

let range = NSRange(0..<inputString.count)
let foundContent = detector.matches(in: inputString, options: [], range: range)

foundContent is an array of NSTextCheckingResult.

We will iterate over foundContent and append found items to results array.

foundContent.forEach { content in
    switch content.resultType {
    case NSTextCheckingResult.CheckingType.link:
        results.append("\(content.url!)")
case NSTextCheckingResult.CheckingType.address:
        let city = content.addressComponents![NSTextCheckingKey.city] ?? ""
        let street =
        content.addressComponents![NSTextCheckingKey.street] ?? ""
        let state = content.addressComponents![NSTextCheckingKey.state] ?? ""
        results.append("Address: \(street), \(city), \(state)")
case NSTextCheckingResult.CheckingType.phoneNumber:
        results.append("Phone Number: \(content.phoneNumber!)")
default:
        break
    }

Complete code should look like this:

struct DevTechieDataDetectorExample: View {
    @State private var inputString = "DevTechie can be reached at devtechieinc@gmail.com. It's located in 123 main street, San Francisco, California. You can call (555)555-5555 for more info."
    @State private var results = [String]()
    
    let detector = try! NSDataDetector(
        types: NSTextCheckingResult.CheckingType.link.rawValue | NSTextCheckingResult.CheckingType.address.rawValue |
      NSTextCheckingResult.CheckingType.phoneNumber.rawValue
    )
    
    var body: some View {
        NavigationStack {
            VStack {
                TextEditor(text: $inputString)
                    .overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.orange.gradient, lineWidth: 2))
                Button("Process") {
                    let range = NSRange(0..<inputString.count)
                    let foundContent = detector.matches(
                      in: inputString,
                      options: [], range: range)
                    
                    foundContent.forEach { content in
                        switch content.resultType {
                        case NSTextCheckingResult.CheckingType.link:
                            results.append("\(content.url!)")
                            
                        case NSTextCheckingResult.CheckingType.address:
                            let city = content.addressComponents![NSTextCheckingKey.city] ?? ""
                            let street =
                            content.addressComponents![NSTextCheckingKey.street] ?? ""
                            let state = content.addressComponents![NSTextCheckingKey.state] ?? ""
                            results.append("Address: \(street), \(city), \(state)")
                            
                        case NSTextCheckingResult.CheckingType.phoneNumber:
                            results.append("Phone Number: \(content.phoneNumber!)")
                            
                        default:
                            break
                        }
                    }
                }
                .buttonStyle(.borderedProminent)
                
                List(results, id: \.self) { item in
                    Text(item)
                }
            }
            .navigationTitle("DevTechie")
            .padding()
        }
    }
}