├── _config.yml ├── Resources └── hero.png ├── TestGround-tvOS.playground ├── contents.xcplayground └── Contents.swift ├── TestGround-iOS.playground ├── playground.xcworkspace │ └── contents.xcworkspacedata ├── contents.xcplayground └── Contents.swift ├── TestGround-macOS.playground ├── playground.xcworkspace │ └── contents.xcworkspacedata ├── contents.xcplayground └── Contents.swift ├── MKDataDetector.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ ├── MKDataDetector.xcscheme │ │ └── MKDataDetectorTests.xcscheme └── project.pbxproj ├── MKDataDetector.xcworkspace ├── xcshareddata │ ├── WorkspaceSettings.xcsettings │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── .travis.yml ├── MKDataDetectorTests ├── TestUtilities.swift ├── Info.plist ├── TransitInformationExtensionTests.swift ├── DateExtensionTests.swift ├── PhoneNumberExtensionTests.swift ├── LinkExtensionTests.swift ├── MultiDataTests.swift ├── StringAccessTests.swift └── AddressExtensionTests.swift ├── .gitignore ├── MKDataDetector ├── MKDataDetector.h ├── ResultType.swift ├── Info.plist ├── AnalysisResult.swift ├── Link.swift ├── PhoneNumber.swift ├── TransitInformation.swift ├── MultiData.swift ├── AliasManager.swift ├── CoreKeys.swift ├── UtilityExtensions.swift ├── Address.swift ├── StringAccess.swift ├── Date.swift └── MKDataDetectorService.swift ├── CHANGELOG.md ├── MKDataDetector.podspec ├── CONTRIBUTING.md ├── LICENSE.md ├── CODE_OF_CONDUCT.md └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /Resources/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayankk2308/mkdatadetector/HEAD/Resources/hero.png -------------------------------------------------------------------------------- /TestGround-tvOS.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TestGround-iOS.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestGround-macOS.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestGround-iOS.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TestGround-macOS.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MKDataDetector.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MKDataDetector.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | xcode_project: MKDataDetector.xcodeproj 4 | xcode_scheme: MKDataDetector 5 | script: xcodebuild clean build -sdk iphonesimulator -workspace MKDataDetector.xcworkspace -scheme MKDataDetector CODE_SIGNING_REQUIRED=NO 6 | -------------------------------------------------------------------------------- /MKDataDetector.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MKDataDetectorTests/TestUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestUtilities.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 22/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | func concatenate(_ stringArray: [String]) -> String { 12 | var string = "" 13 | for item in stringArray { 14 | string += item + ". " 15 | } 16 | return string 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | *.DS_Store 25 | -------------------------------------------------------------------------------- /MKDataDetector.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /MKDataDetector/MKDataDetector.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKDataDetector.h 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/8/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for MKDataDetector. 12 | FOUNDATION_EXPORT double MKDataDetectorVersionNumber; 13 | 14 | //! Project version string for MKDataDetector. 15 | FOUNDATION_EXPORT const unsigned char MKDataDetectorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0.0 2 | * Conforms to Swift **5**. 3 | * Adds support for **tvOS** and **watchOS**. 4 | 5 | # 2.0.2 6 | * Conforms to Swift **4.2**. 7 | 8 | # 2.0.1 9 | * Conforms to Swift **4.1**. 10 | 11 | # 2.0.0 12 | * Migrates to Swift 4.0. 13 | * Removes ability to add to calendar. 14 | * Adds ability to generate `EKEvent` objects instead. 15 | * More concise documentation. 16 | 17 | # 1.0.3 18 | Change Swift version to 3.0 to attempt **CocoaDocs** resolution. 19 | 20 | # 1.0.2 21 | * Documentation update. 22 | * Removal of **(c)** symbol from **swift** files. 23 | 24 | # 1.0.1 25 | Minor documentation changes and **CocoaPods** page fix. 26 | 27 | # 1.0.0 28 | Initial stable release. 29 | -------------------------------------------------------------------------------- /MKDataDetector/ResultType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultType.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/11/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | /// Defines the types of detectable results that are available. 10 | public enum ResultType { 11 | 12 | /// Attempts to locate dates. 13 | case date 14 | 15 | /// Attempts to locate addresses. 16 | case address 17 | 18 | /// Attempts to locate URL links. 19 | case link 20 | 21 | /// Attempts to locate phone numbers. 22 | case phoneNumber 23 | 24 | /// Attempts to locate transit information. 25 | case transitInformation 26 | } 27 | -------------------------------------------------------------------------------- /MKDataDetectorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MKDataDetector/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /MKDataDetector.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'MKDataDetector' 3 | spec.version = '3.0.0' 4 | spec.watchos.deployment_target = '2.0' 5 | spec.tvos.deployment_target = '9.0' 6 | spec.ios.deployment_target = '8.0' 7 | spec.osx.deployment_target = '10.9' 8 | spec.license = { :type => 'MIT', :file => 'LICENSE.md' } 9 | spec.source = { :git => 'https://github.com/mayankk2308/mkdatadetector.git', :tag => spec.version.to_s} 10 | spec.authors = {'Mayank Kumar' => 'mayankk2308@gmail.com', 'Jeet Parte' => 'jeetparte@gmail.com'} 11 | spec.homepage = 'https://mayankk2308.github.io/mkdatadetector/' 12 | spec.summary = 'A Swift wrapper for NSDataDetector that simplifies detection and application of dates, links, addresses, etc. from natural language text.' 13 | spec.source_files = 'MKDataDetector/*.swift' 14 | spec.swift_version = '4.2' 15 | end 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via the **Issues** section. For any interactions, please follow the outlined **Code of Conduct**. 4 | 5 | ## Issues, Requests, & Suggestions 6 | Before creating an issue or requesting features, please check the Issues section if it has already been filed. Please avoid filing duplicate requests. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 11 | 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. 12 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [Semantic Versioning](http://semver.org/) from 1.0.0 onwards. Pre-releases may not adhere to this scheme. 13 | 4. We will review all pull requests and merge them accordingly. 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mayank Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MKDataDetector/AnalysisResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalysisResult.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/10/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | /// Defines types that may be retrieved as analysis result data. 10 | public protocol AnalysisData {} 11 | 12 | extension String: AnalysisData {} 13 | extension Date: AnalysisData {} 14 | extension URL: AnalysisData {} 15 | extension Dictionary: AnalysisData {} 16 | 17 | /// A generic struct used to describe items located by text checking. Each `AnalysisResult` represents an occurence of requested textual content found during analysis of a body of text. 18 | public struct AnalysisResult { 19 | 20 | /// The entire text body in which the matched substring was contained. 21 | public var source: String 22 | 23 | /// The range of the matched portion of the text body. 24 | public var rangeInSource: NSRange 25 | 26 | /// The substring corresponding to the matched portion of the text body. 27 | public var dataString: String 28 | 29 | /// The `ResultType` corresponding to the data being returned. 30 | public var dataType: ResultType 31 | 32 | /// The data extracted from the source input. 33 | public var data: T 34 | } 35 | -------------------------------------------------------------------------------- /MKDataDetector/Link.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Link.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/9/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | extension MKDataDetectorService { 12 | 13 | /// Retrieves URL links matched from a given body of text along with the range of each substring that matched. 14 | /// 15 | /// - Parameter textBody: A body of natural language text. 16 | /// - Returns: An array of `URLAnalysisResult` instances or `nil` if no links could be found. 17 | public func extractLinks(fromTextBody textBody: String) -> [URLAnalysisResult]? { 18 | return extractData(fromTextBody: textBody, withResultTypes: [.link]) 19 | } 20 | 21 | /// Retrieves URL links matched from the given bodies of text along with the range of each substring that matched. 22 | /// 23 | /// - Parameter textBodies: A set of multiple bodies of natural language text. 24 | /// - Returns: An array of `[URLAnalysisResult]` instances or `nil` if no links could be found. 25 | /// - Note: Inside the returned array, a non-empty `[URLAnalysisResult]` instance is returned for each text body which contains a match. 26 | public func extractLinks(fromTextBodies textBodies: [String]) -> [[URLAnalysisResult]]? { 27 | return extractData(fromTextBodies: textBodies, withResultTypes: [.link]) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MKDataDetector/PhoneNumber.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhoneNumber.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/9/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | extension MKDataDetectorService { 12 | 13 | /// Retrieves phone numbers matched from a given body of text along with the range of each substring that matched. 14 | /// 15 | /// - Parameter textBody: A body of natural language text. 16 | /// - Returns: An array of `PhoneNumberAnalysisResult` instances or `nil` if no phone numbers could be found. 17 | public func extractPhoneNumbers(fromTextBody textBody: String) -> [PhoneNumberAnalysisResult]? { 18 | return extractData(fromTextBody: textBody, withResultTypes: [.phoneNumber]) 19 | } 20 | 21 | /// Retrieves phone numbers matched from the given bodies of text along with the range of each substring that matched. 22 | /// 23 | /// - Parameter textBodies: A set of multiple bodies of natural language text. 24 | /// - Returns: An array of `[PhoneNumberAnalysisResult]` instances or `nil` if no phone numbers could be found. 25 | /// - Note: Inside the returned array, a non-empty `[PhoneNumberAnalysisResult]` instance is returned for each text body which contains a match. 26 | public func extractPhoneNumbers(fromTextBodies textBodies: [String]) -> [[PhoneNumberAnalysisResult]]? { 27 | return extractData(fromTextBodies: textBodies, withResultTypes: [.phoneNumber]) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MKDataDetector/TransitInformation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransitInformation.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/9/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | extension MKDataDetectorService { 12 | 13 | /// Retrieves transit information matched from a given body of text along with the range of each substring that matched. 14 | /// 15 | /// - Parameter textBody: A body of natural language text. 16 | /// - Returns: An array of `TransitAnalysisResult` instances or `nil` if no transit information could be found. 17 | public func extractTransitInformation(fromTextBody textBody: String) -> [TransitAnalysisResult]? { 18 | return extractData(fromTextBody: textBody, withResultTypes: [.transitInformation]) 19 | } 20 | 21 | /// Retrieves transit information matched from the given bodies of text along with the range of each substring that matched. 22 | /// 23 | /// - Parameter textBodies: A set of multiple bodies of natural language text. 24 | /// - Returns: An array of `[TransitAnalysisResult]` instances or `nil` if no transit information could be found. 25 | /// - Note: Inside the returned array, a non-empty `[TransitAnalysisResult]` instance is returned for each text body which contains a match. 26 | public func extractTransitInformation(fromTextBodies textBodies: [String]) -> [[TransitAnalysisResult]]? { 27 | return extractData(fromTextBodies: textBodies, withResultTypes: [.transitInformation]) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MKDataDetector/MultiData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiData.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/17/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | extension MKDataDetectorService { 12 | 13 | /// Retrieves multiple types of information from a given body of text along with the range of each substring that matched. 14 | /// 15 | /// - Parameters: 16 | /// - textBody: A body of natural language text. 17 | /// - types: The types of data to detect. 18 | /// - Returns: An array of `GenericAnalysisResult` instances or `nil` if matches could not be found. 19 | public func extractInformation(fromTextBody textBody: String, withResultTypes types: ResultType ...) -> [GenericAnalysisResult]? { 20 | return types.isEmpty ? nil : extractData(fromTextBody: textBody, withResultTypes: types) 21 | } 22 | 23 | /// Retrieves multiple types of information from the given bodies of text along with the range of each substring that matched. 24 | /// 25 | /// - Parameters: 26 | /// - textBodies: An array of multiple bodies of natural language text. 27 | /// - types: The types of data to detect. 28 | /// - Returns: An array of `[GenericAnalysisResult]` instances or `nil` if matches could not be found. 29 | /// - Note: Inside the returned array, a non-empty `[GenericAnalysisResult]` instance is returned for each text body which contains a match. 30 | public func extractInformation(fromTextBodies textBodies: [String], withResultTypes types: ResultType ...) -> [[GenericAnalysisResult]]? { 31 | return types.isEmpty ? nil : extractData(fromTextBodies: textBodies, withResultTypes: types) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MKDataDetector/AliasManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AliasManager.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/19/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT License. 7 | // 8 | 9 | import CoreLocation 10 | 11 | /// An address dictionary returned as part of an `AddressAnalysisResult`. 12 | public typealias AddressInfo = Dictionary 13 | 14 | /// A transit dictionary returned as part of a `TransitAnalysisResult`. 15 | public typealias TransitInfo = Dictionary 16 | 17 | /// An `AnalysisResult` that represents an occurence of a phone number found during analysis of a body of text. 18 | public typealias PhoneNumberAnalysisResult = AnalysisResult 19 | 20 | /// An `AnalysisResult` that represents an occurence of a date found during analysis of a body of text. 21 | public typealias DateAnalysisResult = AnalysisResult 22 | 23 | /// An `AnalysisResult` that represents an occurence of a URL found during analysis of a body of text. 24 | public typealias URLAnalysisResult = AnalysisResult 25 | 26 | /// An `AnalysisResult` that represents an occurence of an address component found during analysis of a body of text. 27 | public typealias AddressAnalysisResult = AnalysisResult 28 | 29 | /// An `AnalysisResult` that represents an occurence of transit information found during analysis of a body of text. 30 | public typealias TransitAnalysisResult = AnalysisResult 31 | 32 | /// An `AnalysisResult` that represents an occurance of any information type that conforms to `AnalysisData`. 33 | public typealias GenericAnalysisResult = AnalysisResult 34 | 35 | /// A completion handler for determining success or failure of a task. 36 | public typealias successCompletion = (Bool) -> Void 37 | 38 | /// A completion handler that calls with a `CLLocation` object or `nil` if the object was not found. 39 | public typealias locationCompletion = (CLLocation?) -> Void 40 | -------------------------------------------------------------------------------- /MKDataDetectorTests/TransitInformationExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransitInformationExtensionTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 23/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | @testable import MKDataDetector 11 | 12 | class TransitInformationTests: XCTestCase { 13 | 14 | let dataDetectorService = MKDataDetectorService() 15 | var textBody = "UA 2392" 16 | var textBodies = ["Flight 2334", "EK 239 to Boston", "Emirates Airlines", "United Flight 2223"] 17 | let type: NSTextCheckingResult.CheckingType = .transitInformation 18 | var nsDataDetector: NSDataDetector! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 23 | } 24 | 25 | func testSingleTextBody() { 26 | let results = dataDetectorService.extractTransitInformation(fromTextBody: textBody) 27 | let expectedCount = nsDataDetector.numberOfMatches(in: textBody, range: NSMakeRange(0, textBody.utf16.count)) 28 | if expectedCount > 0 { 29 | XCTAssertNotNil(results) 30 | XCTAssertEqual(results!.count, expectedCount) 31 | } else { 32 | XCTAssertNil(results) 33 | } 34 | } 35 | 36 | func testMultipleTextBodies() { 37 | let combinedResults = dataDetectorService.extractTransitInformation(fromTextBodies: textBodies) 38 | 39 | //Check no.of matches for all textbodies 40 | let mergedTextBody = concatenate(textBodies) 41 | let totalMatches = nsDataDetector.numberOfMatches(in: mergedTextBody, range: NSMakeRange(0, mergedTextBody.utf16.count)) 42 | if totalMatches > 0 { 43 | XCTAssertNotNil(combinedResults) 44 | for individualResult in combinedResults! { 45 | XCTAssertFalse(individualResult.isEmpty) 46 | 47 | //Check no. of matches for a present textbody 48 | let source = individualResult.first!.source //grab the textbody 49 | let expectedCount = nsDataDetector.numberOfMatches(in: source, range: NSMakeRange(0, source.utf16.count)) 50 | XCTAssertEqual(individualResult.count, expectedCount) 51 | } 52 | } else { 53 | XCTAssertNil(combinedResults) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MKDataDetector/CoreKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreKeys.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/11/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Stores address-related keys for convenient access to an address dictionary (i.e. `AddressInfo` inside an `AddressAnalysisResult`) 12 | public struct Address { 13 | 14 | /// A key that corresponds to the city component of the address. 15 | public static let city = NSTextCheckingKey.city 16 | 17 | /// A key that corresponds to the zip code or postal code component of the address. 18 | public static let zip = NSTextCheckingKey.zip 19 | 20 | /// A key that corresponds to the street address component of the address. 21 | public static let street = NSTextCheckingKey.street 22 | 23 | /// A key that corresponds to the country component of the address. 24 | public static let country = NSTextCheckingKey.country 25 | 26 | /// A key that corresponds to the state or province component of the address. 27 | public static let state = NSTextCheckingKey.state 28 | 29 | // MARK:- Keys for which detection is unavailable in the original NSDataDetector API: 30 | 31 | // /// A key that corresponds to the name component of the address. 32 | // public static let name = NSTextCheckingNameKey 33 | 34 | 35 | // /// A key that corresponds to the phone number component of the address. 36 | // public static let phone = NSTextCheckingPhoneKey 37 | 38 | // /// A key that corresponds to the job component of the address. 39 | // public static let jobTitle = NSTextCheckingJobTitleKey 40 | 41 | 42 | // /// A key that corresponds to the organization component of the address. 43 | // public static let organization = NSTextCheckingOrganizationKey 44 | } 45 | 46 | /// Stores transit-related keys for convenient access to a transit dictionary (i.e. `TransitInfo` inside a `TransitAnalysisResult` 47 | public struct Transit { 48 | 49 | /// A key that corresponds to the flight component of a transit result. 50 | public static let flight = NSTextCheckingKey.flight 51 | 52 | //Keys for which detection is unavailable in the original NSDataDetector API: 53 | 54 | // /// A key that corresponds to the airline of a transit result. 55 | // public static let airline = NSTextCheckingAirlineKey 56 | 57 | } 58 | -------------------------------------------------------------------------------- /MKDataDetectorTests/DateExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateExtensionTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 22/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | @testable import MKDataDetector 11 | 12 | class DateTests: XCTestCase { 13 | 14 | let dataDetectorService = MKDataDetectorService() 15 | var textBody = "I have a program at 7pm this Sunday, which ends on 11am Monday" 16 | var textBodies = ["Program", "Event at 9pm tomorrow", "11am party"] 17 | let type: NSTextCheckingResult.CheckingType = .date 18 | var nsDataDetector: NSDataDetector! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 23 | 24 | //Change textbodies if required 25 | // textBody = "" 26 | // textBodies = [] 27 | } 28 | 29 | func testSingleTextBody() { 30 | let results = dataDetectorService.extractDates(fromTextBody: textBody) 31 | let expectedCount = nsDataDetector.numberOfMatches(in: textBody, range: NSMakeRange(0, textBody.utf16.count)) 32 | if expectedCount > 0 { 33 | XCTAssertNotNil(results) 34 | XCTAssertEqual(results!.count, expectedCount) 35 | } else { 36 | XCTAssertNil(results) 37 | } 38 | } 39 | 40 | func testMultipleTextBodies() { 41 | let combinedResults = dataDetectorService.extractDates(fromTextBodies: textBodies) 42 | 43 | //Check no.of matches for all textbodies 44 | let mergedTextBody = concatenate(textBodies) 45 | let totalMatches = nsDataDetector.numberOfMatches(in: mergedTextBody, range: NSMakeRange(0, mergedTextBody.utf16.count)) 46 | if totalMatches > 0 { 47 | XCTAssertNotNil(combinedResults) 48 | for individualResult in combinedResults! { 49 | XCTAssertFalse(individualResult.isEmpty) 50 | 51 | //Check no. of matches for a present textbody 52 | let source = individualResult.first!.source //grab the textbody 53 | let expectedCount = nsDataDetector.numberOfMatches(in: source, range: NSMakeRange(0, source.utf16.count)) 54 | XCTAssertEqual(individualResult.count, expectedCount) 55 | } 56 | } else { 57 | XCTAssertNil(combinedResults) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MKDataDetectorTests/PhoneNumberExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhoneNumberExtensionTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 23/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | @testable import MKDataDetector 11 | 12 | class PhoneNumberTests: XCTestCase { 13 | 14 | let dataDetectorService = MKDataDetectorService() 15 | var textBody = "Mayank: +1 (413) 801 6324" 16 | var textBodies = ["+14138016324", "Mayank: +91 9920095040", "400053"] 17 | let type: NSTextCheckingResult.CheckingType = .phoneNumber 18 | var nsDataDetector: NSDataDetector! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 23 | 24 | //Change textbodies if required 25 | // textBody = "" 26 | // textBodies = [] 27 | } 28 | 29 | func testSingleTextBody() { 30 | let results = dataDetectorService.extractPhoneNumbers(fromTextBody: textBody) 31 | let expectedCount = nsDataDetector.numberOfMatches(in: textBody, range: NSMakeRange(0, textBody.utf16.count)) 32 | if expectedCount > 0 { 33 | XCTAssertNotNil(results) 34 | XCTAssertEqual(results!.count, expectedCount) 35 | } else { 36 | XCTAssertNil(results) 37 | } 38 | } 39 | 40 | func testMultipleTextBodies() { 41 | let combinedResults = dataDetectorService.extractPhoneNumbers(fromTextBodies: textBodies) 42 | 43 | //Check no.of matches for all textbodies 44 | let mergedTextBody = concatenate(textBodies) 45 | let totalMatches = nsDataDetector.numberOfMatches(in: mergedTextBody, range: NSMakeRange(0, mergedTextBody.utf16.count)) 46 | if totalMatches > 0 { 47 | XCTAssertNotNil(combinedResults) 48 | for individualResult in combinedResults! { 49 | XCTAssertFalse(individualResult.isEmpty) 50 | 51 | //Check no. of matches for a present textbody 52 | let source = individualResult.first!.source //grab the textbody 53 | let expectedCount = nsDataDetector.numberOfMatches(in: source, range: NSMakeRange(0, source.utf16.count)) 54 | XCTAssertEqual(individualResult.count, expectedCount) 55 | } 56 | } else { 57 | XCTAssertNil(combinedResults) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MKDataDetector/UtilityExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataDetectorType.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/10/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | extension MKDataDetectorService { 12 | 13 | /// Supporting utility for generating an appropriate detector. 14 | /// 15 | /// - Parameter resultTypes: The types to detect. 16 | /// - Returns: An `NSDataDetector` or `nil` if it could not be instantiated. 17 | internal func dataDetectorOfType(withResultTypes resultTypes: [ResultType]) -> NSDataDetector? { 18 | do { 19 | var checkingTypes = NSTextCheckingResult.CheckingType() 20 | for resultType in resultTypes { 21 | guard let type = checkingTypeMap[resultType] else { return nil } 22 | checkingTypes.insert(type) 23 | } 24 | return checkingTypes.isEmpty ? nil : try NSDataDetector(types: checkingTypes.rawValue) 25 | } catch { return nil } 26 | } 27 | 28 | 29 | /// Supporting utility for retrieving results from matches with appropriate type. 30 | /// 31 | /// - Parameters: 32 | /// - match: An `NSTextCheckingResult` instance. 33 | /// - type: The type of match. 34 | /// - Returns: The result of the match casted generically. 35 | internal func retrieveData(fromMatch match: NSTextCheckingResult, withMatchType type: NSTextCheckingResult.CheckingType) -> T? { 36 | switch type { 37 | case NSTextCheckingResult.CheckingType.date: 38 | return match.date as? T 39 | case NSTextCheckingResult.CheckingType.address: 40 | return match.addressComponents as? T 41 | case NSTextCheckingResult.CheckingType.link: 42 | return match.url as? T 43 | case NSTextCheckingResult.CheckingType.phoneNumber: 44 | return match.phoneNumber as? T 45 | default: 46 | return match.components as? T 47 | } 48 | } 49 | 50 | /// Supporting utility for generating a substring from a string given a range. 51 | /// 52 | /// - Parameters: 53 | /// - textBody: A body of natural language text. 54 | /// - range: The range of the target substring. 55 | /// - Returns: A substring of `textBody` based on the `range`. 56 | internal func extractSource(fromTextBody textBody: String, usingRange range: NSRange) -> String { 57 | return (textBody as NSString).substring(with: range) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /MKDataDetectorTests/LinkExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinkExtensionTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 22/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | @testable import MKDataDetector 11 | 12 | class LinkTests: XCTestCase { 13 | 14 | let dataDetectorService = MKDataDetectorService() 15 | var textBody = "My LinkedIn profile is https://www.linkedin.com/in/mayank-kumar-478245b1/, but my Github is at www.github.com/mayankk2308" 16 | var textBodies = ["My LinkedIn profile is https://www.linkedin.com/in/mayank-kumar-478245b1/", "My Github is at www.github.com/mayankk2308", "This has no links!"] 17 | let type: NSTextCheckingResult.CheckingType = .link 18 | var nsDataDetector: NSDataDetector! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 23 | 24 | //Change textbodies if required 25 | // textBody = "" 26 | // textBodies = [] 27 | } 28 | 29 | func testSingleTextBody() { 30 | let results = dataDetectorService.extractLinks(fromTextBody: textBody) 31 | let expectedCount = nsDataDetector.numberOfMatches(in: textBody, range: NSMakeRange(0, textBody.utf16.count)) 32 | if expectedCount > 0 { 33 | XCTAssertNotNil(results) 34 | XCTAssertEqual(results!.count, expectedCount) 35 | } else { 36 | XCTAssertNil(results) 37 | } 38 | } 39 | 40 | func testMultipleTextBodies() { 41 | let combinedResults = dataDetectorService.extractLinks(fromTextBodies: textBodies) 42 | 43 | //Check no.of matches for all textbodies 44 | let mergedTextBody = concatenate(textBodies) 45 | let totalMatches = nsDataDetector.numberOfMatches(in: mergedTextBody, range: NSMakeRange(0, mergedTextBody.utf16.count)) 46 | if totalMatches > 0 { 47 | XCTAssertNotNil(combinedResults) 48 | for individualResult in combinedResults! { 49 | XCTAssertFalse(individualResult.isEmpty) 50 | 51 | //Check no. of matches for a present textbody 52 | let source = individualResult.first!.source //grab the textbody 53 | let expectedCount = nsDataDetector.numberOfMatches(in: source, range: NSMakeRange(0, source.utf16.count)) 54 | XCTAssertEqual(individualResult.count, expectedCount) 55 | } 56 | } else { 57 | XCTAssertNil(combinedResults) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MKDataDetectorTests/MultiDataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiDataTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 23/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | @testable import MKDataDetector 11 | 12 | class MultiDataTests: XCTestCase { 13 | 14 | let dataDetectorService = MKDataDetectorService() 15 | var mixedtextBody = "Call me on +1123456789 on 2017/13/12 at 5pm when you reach 990 Belchertown Rd, Amherst, MA - 01440 USA. Checkout out my website www.example.com. I'm flying to Paris tomorrow by Flight 2223" 16 | var mixedTextBodies = ["Call me on +1123456789", "on 2017/13/12 at 5pm", "when you reach 990 Belchertown Rd, Amherst, MA - 01440 USA", "Checkout out my website www.example.com", "I'm flying to Paris tomorrow by Flight 2223"] 17 | var types: NSTextCheckingResult.CheckingType! 18 | var nsDataDetector: NSDataDetector! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | types = [.date, .address, .link, .phoneNumber, .transitInformation] 23 | nsDataDetector = try! NSDataDetector(types: types.rawValue) 24 | 25 | //Change textbodies here 26 | // mixedtextBody = "" 27 | // mixedTextBodies = [] 28 | } 29 | func testSingleTextBody() { 30 | let results = dataDetectorService.extractInformation(fromTextBody: mixedtextBody, withResultTypes: .date, .address, .link, .phoneNumber, .transitInformation) 31 | let expectedCount = nsDataDetector.numberOfMatches(in: mixedtextBody, range: NSMakeRange(0, mixedtextBody.utf16.count)) 32 | if expectedCount > 0 { 33 | XCTAssertNotNil(results) 34 | XCTAssertEqual(results!.count, expectedCount) 35 | } else { 36 | XCTAssertNil(results) 37 | } 38 | } 39 | 40 | func testMultipleTextBodies() { 41 | let combinedResults = dataDetectorService.extractInformation(fromTextBodies: mixedTextBodies, withResultTypes: .date, .address, .link, .phoneNumber, .transitInformation) 42 | 43 | //Check no.of matches for all textbodies 44 | let mergedTextBody = concatenate(mixedTextBodies) 45 | let totalMatches = nsDataDetector.numberOfMatches(in: mergedTextBody, range: NSMakeRange(0, mergedTextBody.utf16.count)) 46 | if totalMatches > 0 { 47 | XCTAssertNotNil(combinedResults) 48 | for individualResult in combinedResults! { 49 | XCTAssertFalse(individualResult.isEmpty) 50 | 51 | //Check no. of matches for a present textbody 52 | let source = individualResult.first!.source //grab the textbody 53 | let expectedCount = nsDataDetector.numberOfMatches(in: source, range: NSMakeRange(0, source.utf16.count)) 54 | XCTAssertEqual(individualResult.count, expectedCount) 55 | } 56 | } else { 57 | XCTAssertNil(combinedResults) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MKDataDetector.xcodeproj/xcshareddata/xcschemes/MKDataDetector.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mayankk2308@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /MKDataDetector/Address.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Address.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/9/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | extension MKDataDetectorService { 13 | 14 | /// Retrieves address components matched from a given body of text along with the range of each substring that matched. 15 | /// 16 | /// - Parameter textBody: A body of natural language text. 17 | /// - Returns: An array of `AddressAnalysisResult` instances or `nil` if no address components could be found. 18 | public func extractAddresses(fromTextBody textBody: String) -> [AddressAnalysisResult]? { 19 | return extractData(fromTextBody: textBody, withResultTypes: [.address]) 20 | } 21 | 22 | /// Retrieves address components matched from the given bodies of text along with the range of each substring that matched. 23 | /// 24 | /// - Parameter textBodies: A set of multiple bodies of natural language text. 25 | /// - Returns: An array of `[AddressAnalysisResult]` instances or `nil` if no address components could be found. 26 | /// - Note: Inside the returned array, a non-empty `[AddressAnalysisResult]` instance is returned for each text body which contains a match. 27 | public func extractAddresses(fromTextBodies textBodies: [String]) -> [[AddressAnalysisResult]]? { 28 | return extractData(fromTextBodies: textBodies, withResultTypes: [.address]) 29 | } 30 | 31 | /// Retrieves the geographic location of an address analysis result. 32 | /// 33 | /// - Parameters: 34 | /// - result: The address analysis result. 35 | /// - completion: Called with a `CLLocation` object, which will be `nil` if it could not be generated. 36 | public func extractLocation(fromAnalysisResult result: AddressAnalysisResult, onCompletion completion: @escaping locationCompletion) { 37 | extractLocation(fromAddress: result.source, onCompletion: completion) 38 | } 39 | 40 | /// Retrieves the geographic location of a valid address. 41 | /// 42 | /// - Parameters: 43 | /// - address: A valid address. 44 | /// - completion: Called with a `CLLocation` object, which will be `nil` if it could not be generated. 45 | public func extractLocation(fromAddress address: String, onCompletion completion: @escaping locationCompletion) { 46 | let geocoder = CLGeocoder() 47 | geocoder.geocodeAddressString(address) { placemarks, error in 48 | guard let location = self.extractLocation(fromPlacemarks: placemarks, withError: error) else { return } 49 | completion(location) 50 | } 51 | } 52 | 53 | /// Retrieves the geographic location obtained from the first placemark. 54 | /// 55 | /// - Parameters: 56 | /// - placemarks: An array of placemarks. 57 | /// - error: An error that may have occurred in retrieval. 58 | /// - Returns: A `CLLocation` object or `nil` if it could not be generated. 59 | internal func extractLocation(fromPlacemarks placemarks: [CLPlacemark]?, withError error: Error?) -> CLLocation? { 60 | if error == nil { 61 | guard let placemarks = placemarks, let placemark = placemarks.first else { return nil } 62 | return placemark.location 63 | } 64 | return nil 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /MKDataDetectorTests/StringAccessTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringAccessTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 23/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | @testable import MKDataDetector 11 | 12 | class StringAccessTests: XCTestCase { 13 | 14 | var testString = "This test was written at 1:34 PM" 15 | var type: NSTextCheckingResult.CheckingType! 16 | var nsDataDetector: NSDataDetector! 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | //Change test string here 22 | // testString = "+14138016324" 23 | // testString = "" 24 | } 25 | 26 | func testDates() { 27 | let dates = testString.dates 28 | type = .date 29 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 30 | let expectedCount = nsDataDetector.numberOfMatches(in: testString, range: NSMakeRange(0, testString.utf16.count)) 31 | if expectedCount > 0 { 32 | XCTAssertNotNil(dates) 33 | XCTAssertEqual(dates!.count, expectedCount) 34 | } else { 35 | XCTAssertNil(dates) 36 | } 37 | } 38 | 39 | func testLinks() { 40 | let links = testString.links 41 | type = .link 42 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 43 | let expectedCount = nsDataDetector.numberOfMatches(in: testString, range: NSMakeRange(0, testString.utf16.count)) 44 | if expectedCount > 0 { 45 | XCTAssertNotNil(links) 46 | XCTAssertEqual(links!.count, expectedCount) 47 | } else { 48 | XCTAssertNil(links) 49 | } 50 | } 51 | 52 | func testAddresses() { 53 | let addresses = testString.addresses 54 | type = .address 55 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 56 | let expectedCount = nsDataDetector.numberOfMatches(in: testString, range: NSMakeRange(0, testString.utf16.count)) 57 | if expectedCount > 0 { 58 | XCTAssertNotNil(addresses) 59 | XCTAssertEqual(addresses!.count, expectedCount) 60 | } else { 61 | XCTAssertNil(addresses) 62 | } 63 | } 64 | 65 | func testPhoneNumbers() { 66 | let phoneNumbers = testString.phoneNumbers 67 | type = .phoneNumber 68 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 69 | let expectedCount = nsDataDetector.numberOfMatches(in: testString, range: NSMakeRange(0, testString.utf16.count)) 70 | if expectedCount > 0 { 71 | XCTAssertNotNil(phoneNumbers) 72 | XCTAssertEqual(phoneNumbers!.count, expectedCount) 73 | } else { 74 | XCTAssertNil(phoneNumbers) 75 | } 76 | } 77 | 78 | func testTransitInfo() { 79 | let transitInfo = testString.transitInfo 80 | type = .transitInformation 81 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 82 | let expectedCount = nsDataDetector.numberOfMatches(in: testString, range: NSMakeRange(0, testString.utf16.count)) 83 | if expectedCount > 0 { 84 | XCTAssertNotNil(transitInfo) 85 | XCTAssertEqual(transitInfo!.count, expectedCount) 86 | } else { 87 | XCTAssertNil(transitInfo) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MKDataDetector/StringAccess.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringAccess.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/16/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | #if os(OSX) 11 | import Cocoa 12 | #else 13 | import UIKit 14 | #endif 15 | 16 | public extension String { 17 | 18 | /// Retrieves an array of matched dates in the string or `nil` if no matches could be found. 19 | var dates: [Date]? { 20 | return retrieveMappedData(withResultType: .date) 21 | } 22 | 23 | /// Retrieves an array of matched links in the string or `nil` if no matches could be found. 24 | var links: [URL]? { 25 | return retrieveMappedData(withResultType: .link) 26 | } 27 | 28 | /// Retrieves an array of matched addresses in the string or `nil` if no matches could be found. 29 | var addresses: [AddressInfo]? { 30 | return retrieveMappedData(withResultType: .address) 31 | } 32 | 33 | /// Retrieves an array of matched phone numbers in the string or `nil` if no matches could be found. 34 | var phoneNumbers: [String]? { 35 | return retrieveMappedData(withResultType: .phoneNumber) 36 | } 37 | 38 | /// Retrieves an array of matched transit information in the string or `nil` if no matches could be found. 39 | var transitInfo: [TransitInfo]? { 40 | return retrieveMappedData(withResultType: .transitInformation) 41 | } 42 | 43 | /// Retrieves generic data for a requested result type. 44 | /// 45 | /// - Parameter type: The type to match. 46 | /// - Returns: An array of generic results or `nil` if no matches could be found. 47 | private func retrieveMappedData(withResultType type: ResultType) -> [T]? { 48 | let dataDetectorService = MKDataDetectorService() 49 | guard let results: [AnalysisResult] = dataDetectorService.extractData(fromTextBody: self, withResultTypes: [type]) else { return nil } 50 | return results.compactMap { $0.data } 51 | } 52 | 53 | } 54 | 55 | internal extension String { 56 | 57 | /// The string with additional whitespaces condensed to a single whitespace. 58 | var condensedWhitespace: String { 59 | let components = self.components(separatedBy: .whitespacesAndNewlines) 60 | return components.filter { !$0.isEmpty }.joined(separator: " ") 61 | } 62 | 63 | } 64 | 65 | extension MKDataDetectorService { 66 | 67 | /// Generates attributed text for the user interface given a set of analysis results on a text body. 68 | /// 69 | /// - Parameters: 70 | /// - results: An array of analysis results. 71 | /// - color: A color for highlighting matches. 72 | /// - Returns: An attributed string on the basis of the matches. 73 | public func attributedText(fromAnalysisResults results: [AnalysisResult], withColor color: CGColor) -> NSMutableAttributedString? { 74 | guard let firstResult = results.first else { return nil } 75 | let attributedString = NSMutableAttributedString(string: firstResult.source) 76 | for result in results { 77 | #if os(iOS) 78 | attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor(cgColor: color), range: result.rangeInSource) 79 | #elseif os(OSX) 80 | attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor(cgColor: color) as Any, range: result.rangeInSource) 81 | #endif 82 | } 83 | return attributedString 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /MKDataDetector/Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/9/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | #if !os(tvOS) 10 | import EventKit 11 | #endif 12 | import Foundation 13 | 14 | extension MKDataDetectorService { 15 | 16 | /// Retrieves dates matched from a given body of text along with the range of each substring that matched. 17 | /// 18 | /// - Parameter textBody: A body of natural language text. 19 | /// - Returns: An array of `DateAnalysisResult` instances or `nil` if no dates could be found. 20 | public func extractDates(fromTextBody textBody: String) -> [DateAnalysisResult]? { 21 | return extractData(fromTextBody: textBody, withResultTypes: [.date]) 22 | } 23 | 24 | /// Retrieves dates matched from the given bodies of text along with the range of each substring that matched. 25 | /// 26 | /// - Parameter textBodies: A set of multiple bodies of natural language text. 27 | /// - Returns: An array of `[DateAnalysisResult]` instances or `nil` if no dates could be found. 28 | /// - Note: Inside the returned array, a non-empty `[DateAnalysisResult]` instance is returned for each text body which contains a match. 29 | public func extractDates(fromTextBodies textBodies: [String]) -> [[DateAnalysisResult]]? { 30 | return extractData(fromTextBodies: textBodies, withResultTypes: [.date]) 31 | } 32 | 33 | #if !os(tvOS) 34 | /// Generates an `EKEvent` for a given analysis result and event store. 35 | /// 36 | /// - Parameters: 37 | /// - store: The event store for which to generate the event. 38 | /// - result: The `DateAnalysisResult` previously procured from analysis. 39 | /// - endDate: The user-specified end date - defaults to an hour from start date otherwise. 40 | /// - Returns: The generated event. 41 | /// 42 | /// - Note: This function auto-generates the event name based on date analysis. This feature requires additional testing and may not always yield satisfactory results. The default generator is exposed for use. 43 | public func generateEvent(forEventStore store: EKEventStore, withAnalysisResult result: DateAnalysisResult, withEndDate endDate: Date? = nil) -> EKEvent { 44 | let eventName = (result.source as NSString).replacingCharacters(in: result.rangeInSource, with: "").condensedWhitespace.trimmingCharacters(in: .whitespaces) 45 | let extractedDate: Date = endDate != nil ? endDate! : result.data.addingTimeInterval(3600) 46 | return generateEvent(forEventStore: store, withEventName: eventName, withStartDate: result.data, withEndDate: extractedDate) 47 | } 48 | 49 | 50 | /// Generates an `EKEvent` for a given set of precise data along with an event store. 51 | /// 52 | /// - Parameters: 53 | /// - store: The event store for which to generate the event. 54 | /// - name: The name of the event. 55 | /// - startDate: The start date/time of the event. 56 | /// - endDate: The end date/time of the event. 57 | /// - Returns: The generated event. 58 | public func generateEvent(forEventStore store: EKEventStore, withEventName name: String, withStartDate startDate: Date, withEndDate endDate: Date) -> EKEvent { 59 | let event = EKEvent(eventStore: store) 60 | event.title = name 61 | event.startDate = startDate 62 | event.endDate = endDate 63 | return event 64 | } 65 | #endif 66 | 67 | } 68 | -------------------------------------------------------------------------------- /MKDataDetector.xcodeproj/xcshareddata/xcschemes/MKDataDetectorTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /MKDataDetector/MKDataDetectorService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKDataDetectorService.swift 3 | // MKDataDetector 4 | // 5 | // Created by Mayank Kumar on 7/8/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The primary service class that drives MKDataDetector capabilities. 12 | public class MKDataDetectorService { 13 | 14 | /// Maps `ResultType` types to `NSTextCheckingResult.CheckingType` types. 15 | internal let checkingTypeMap = [ 16 | ResultType.date: NSTextCheckingResult.CheckingType.date, 17 | ResultType.address: NSTextCheckingResult.CheckingType.address, 18 | ResultType.link: NSTextCheckingResult.CheckingType.link, 19 | ResultType.phoneNumber: NSTextCheckingResult.CheckingType.phoneNumber, 20 | ResultType.transitInformation: NSTextCheckingResult.CheckingType.transitInformation 21 | ] 22 | 23 | /// Maps `NSTextCheckingResult.CheckingType.RawValue` types to `ResultType` types. 24 | internal let inverseCheckingTypeMap: [NSTextCheckingResult.CheckingType.RawValue : ResultType] = [ 25 | NSTextCheckingResult.CheckingType.date.rawValue: ResultType.date, 26 | NSTextCheckingResult.CheckingType.address.rawValue: ResultType.address, 27 | NSTextCheckingResult.CheckingType.link.rawValue: ResultType.link, 28 | NSTextCheckingResult.CheckingType.phoneNumber.rawValue: ResultType.phoneNumber, 29 | NSTextCheckingResult.CheckingType.transitInformation.rawValue: ResultType.transitInformation 30 | ] 31 | 32 | /// Initializes and returns an `MKDataDetectorService` instance. 33 | public init() {} 34 | 35 | /// Generic implementation for extracting data from a single body of text. 36 | /// 37 | /// - Parameters: 38 | /// - textBody: A body of natural language text. 39 | /// - detector: The detector to use for extraction. 40 | /// - types: Types to match. 41 | /// - Returns: A generic array of `AnalysisResult` instances or `nil` if no matches could be found. 42 | /// - Note: The `detector` parameter is optional, and is only useful for preventing redundant generation of the data detector in subsequent requests to this function. 43 | internal func extractData(fromTextBody textBody: String, withDetector detector: NSDataDetector? = nil, withResultTypes types: [ResultType]) -> [AnalysisResult]? { 44 | var analysisResults = [AnalysisResult]() 45 | let dataDetector: NSDataDetector 46 | if detector != nil { 47 | dataDetector = detector! 48 | } else { 49 | guard let newDetector = dataDetectorOfType(withResultTypes: types) else { return nil } 50 | dataDetector = newDetector 51 | } 52 | let matches = dataDetector.matches(in: textBody, range: NSRange(location: 0, length: (textBody as NSString).length)) 53 | if matches.isEmpty { 54 | return nil 55 | } else { 56 | for match in matches { 57 | let range = match.range 58 | let source = textBody 59 | let dataString = extractSource(fromTextBody: textBody, usingRange: range) 60 | guard let data: T = retrieveData(fromMatch: match, withMatchType: match.resultType) else { continue } 61 | guard let resultType = inverseCheckingTypeMap[match.resultType.rawValue] else { continue } 62 | let analysisResult = AnalysisResult(source: source, rangeInSource: range, dataString: dataString, dataType: resultType, data: data) 63 | analysisResults.append(analysisResult) 64 | } 65 | } 66 | return analysisResults.isEmpty ? nil : analysisResults 67 | } 68 | 69 | /// Generic implementation for extracting data from a multiple bodies of text. 70 | /// 71 | /// - Parameters: 72 | /// - textBodies: An array of multiple bodies of natural language text. 73 | /// - types: The types to match. 74 | /// - Returns: A generic array of `[AnalysisResult]` instances or `nil` if no matches could be found in any of the text bodies. 75 | /// - Note: Inside the returned array, a non-empty `[AnalysisResult]` instance is returned for each text body which contains a match. 76 | internal func extractData(fromTextBodies textBodies: [String], withResultTypes types: [ResultType]) -> [[AnalysisResult]]? { 77 | var result = [[AnalysisResult]]() 78 | guard let detector = dataDetectorOfType(withResultTypes: types) else { return nil } 79 | for textBody in textBodies { 80 | guard let extractedData: [AnalysisResult] = extractData(fromTextBody: textBody, withDetector: detector, withResultTypes: types) else { continue } 81 | result.append(extractedData) 82 | } 83 | return result.isEmpty ? nil : result 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /TestGround-iOS.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // Test below - delete tests before commits, or exclude this file in your commits 2 | import MKDataDetector 3 | import CoreLocation 4 | import UIKit 5 | 6 | let dataDetectorService = MKDataDetectorService() 7 | 8 | let textBody = "hello world at 9pm tomorrow" 9 | let textBodies = ["event on monday", "movie on wednesday", "payment due next thursday", "time", "money", "yellowstone park visit next friday", "party next friday at 8pm"] 10 | 11 | let address = "5123 Kere street, get here fast MA 01003, USA" 12 | let legalAddress = "133 Belchertown Rd Amherst MA - 01003 United States" 13 | let addresses = ["B713, Samartha Aangan II, Oshiwara, Andheri West, Mumbai - 400053, India", "133 Belchertown Rd Amherst MA - 01003 United States"] 14 | 15 | let link = "Mayank's Github: https://github.com/mayankk2308" 16 | let links = [link, "another link ://ssdsd", "testlink://", "www.apple.com", "http://apple.com"] 17 | 18 | let phone = "Mayank: +1 (413) 801 6324" 19 | let phones = ["+14138016324", "Mayank: +91 9920095040", "400053"] 20 | 21 | let transit = "UA 2392" 22 | let transits = ["Flight 2334", "EK 239 to Boston", "Emirates Airlines", "United Flight 2223"] 23 | 24 | print("-----Single Body Date Results-----\n") 25 | if let singleBodyDateResults = dataDetectorService.extractDates(fromTextBody: textBody) { 26 | for result in singleBodyDateResults { 27 | print(result.source) 28 | print(result.data) 29 | print() 30 | } 31 | } 32 | 33 | print("\n-----Multiple Bodies Date Results-----\n") 34 | 35 | if let combinedDateResults = dataDetectorService.extractDates(fromTextBodies: textBodies) { 36 | for individualResults in combinedDateResults { 37 | for result in individualResults { 38 | print(result.source) 39 | print(result.data) 40 | print() 41 | } 42 | } 43 | } 44 | 45 | print("\n-----Single Body Address Results-----\n") 46 | 47 | if let addressResults = dataDetectorService.extractAddresses(fromTextBody: address) { 48 | for result in addressResults { 49 | print(result.source) 50 | print(result.data) 51 | print() 52 | } 53 | } 54 | 55 | print("\n-----Multiple Bodies Address Results-----\n") 56 | 57 | if let combinedAddressResults = dataDetectorService.extractAddresses(fromTextBodies: addresses) { 58 | for individualResults in combinedAddressResults { 59 | for result in individualResults { 60 | print(result.source) 61 | print(result.data) 62 | print() 63 | } 64 | } 65 | } 66 | 67 | print("\n-----Single Body URL Results-----\n") 68 | 69 | if let URLResults = dataDetectorService.extractLinks(fromTextBody: link) { 70 | for result in URLResults { 71 | print(result.source) 72 | print(result.data) 73 | print() 74 | } 75 | } 76 | 77 | print("\n-----Multiple Bodies URL Results-----\n") 78 | 79 | if let combinedURLResults = dataDetectorService.extractLinks(fromTextBodies: links) { 80 | for individualResults in combinedURLResults { 81 | for result in individualResults { 82 | print(result.source) 83 | print(result.data) 84 | print() 85 | } 86 | } 87 | } 88 | 89 | print("\n-----Single Body Phone Results-----\n") 90 | 91 | if let phoneResults = dataDetectorService.extractPhoneNumbers(fromTextBody: phone) { 92 | for result in phoneResults { 93 | print(result.source) 94 | print(result.data) 95 | print() 96 | } 97 | } 98 | 99 | print("\n-----Multiple Bodies Phone Results-----\n") 100 | 101 | if let combinedPhoneResults = dataDetectorService.extractPhoneNumbers(fromTextBodies: phones) { 102 | for individualResults in combinedPhoneResults { 103 | for result in individualResults { 104 | print(result.source) 105 | print(result.data) 106 | print() 107 | } 108 | } 109 | } 110 | 111 | print("\n-----Single Body Transit Results-----\n") 112 | 113 | if let transitResults = dataDetectorService.extractTransitInformation(fromTextBody: transit) { 114 | for result in transitResults { 115 | print(result.source) 116 | print(result.data) 117 | print() 118 | } 119 | } 120 | 121 | print("\n-----Multiple Bodies Transit Results-----\n") 122 | 123 | if let combinedTransitResults = dataDetectorService.extractTransitInformation(fromTextBodies: transits) { 124 | for individualResults in combinedTransitResults { 125 | for result in individualResults { 126 | print(result.source) 127 | print(result.data) 128 | print() 129 | } 130 | } 131 | } 132 | 133 | print("\n-----Basic Scenario Implementation Results-----\n") 134 | 135 | print(textBody.dates!) 136 | print(link.links!) 137 | print(transit.transitInfo!) 138 | print(address.addresses!) 139 | print(phone.phoneNumbers!) 140 | 141 | print("\n-----Mixed Request Results-----\n") 142 | 143 | let mixedText = "Call Mayank on +1123456789 on 2017/13/12 at 5pm when you reach 990 Belchertown Rd, Amherst, MA - 01440 USA." 144 | 145 | if let results = dataDetectorService.extractInformation(fromTextBody: mixedText, withResultTypes: .date, .address, .phoneNumber) { 146 | for result in results { 147 | switch result.dataType { 148 | case .date: 149 | let date = result.data as! Date 150 | print(date) 151 | break 152 | case .address: 153 | let address = result.data as! AddressInfo 154 | print(address) 155 | break 156 | case .phoneNumber: 157 | let phoneNumber = result.data as! String 158 | print(phoneNumber) 159 | break 160 | default: 161 | continue 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /TestGround-macOS.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // Test below - delete tests before commits, or exclude this file in your commits 2 | import MKDataDetector 3 | import CoreLocation 4 | import Cocoa 5 | 6 | let dataDetectorService = MKDataDetectorService() 7 | 8 | let textBody = "hello world at 9pm tomorrow" 9 | let textBodies = ["event on monday", "movie on wednesday", "payment due next thursday", "time", "money", "yellowstone park visit next friday", "party next friday at 8pm"] 10 | 11 | let address = "5123 Kere street, get here fast MA 01003, USA" 12 | let legalAddress = "133 Belchertown Rd Amherst MA - 01003 United States" 13 | let addresses = ["B713, Samartha Aangan II, Oshiwara, Andheri West, Mumbai - 400053, India", "133 Belchertown Rd Amherst MA - 01003 United States"] 14 | 15 | let link = "Mayank's Github: https://github.com/mayankk2308" 16 | let links = [link, "another link ://ssdsd", "testlink://", "www.apple.com", "http://apple.com"] 17 | 18 | let phone = "Mayank: +1 (413) 801 6324" 19 | let phones = ["+14138016324", "Mayank: +91 9920095040", "400053"] 20 | 21 | let transit = "UA 2392" 22 | let transits = ["Flight 2334", "EK 239 to Boston", "Emirates Airlines", "United Flight 2223"] 23 | 24 | print("-----Single Body Date Results-----\n") 25 | if let singleBodyDateResults = dataDetectorService.extractDates(fromTextBody: textBody) { 26 | for result in singleBodyDateResults { 27 | print(result.source) 28 | print(result.data) 29 | print() 30 | } 31 | } 32 | 33 | print("\n-----Multiple Bodies Date Results-----\n") 34 | 35 | if let combinedDateResults = dataDetectorService.extractDates(fromTextBodies: textBodies) { 36 | for individualResults in combinedDateResults { 37 | for result in individualResults { 38 | print(result.source) 39 | print(result.data) 40 | print() 41 | } 42 | } 43 | } 44 | 45 | print("\n-----Single Body Address Results-----\n") 46 | 47 | if let addressResults = dataDetectorService.extractAddresses(fromTextBody: address) { 48 | for result in addressResults { 49 | print(result.source) 50 | print(result.data) 51 | print() 52 | } 53 | } 54 | 55 | print("\n-----Multiple Bodies Address Results-----\n") 56 | 57 | if let combinedAddressResults = dataDetectorService.extractAddresses(fromTextBodies: addresses) { 58 | for individualResults in combinedAddressResults { 59 | for result in individualResults { 60 | print(result.source) 61 | print(result.data) 62 | print() 63 | } 64 | } 65 | } 66 | 67 | print("\n-----Single Body URL Results-----\n") 68 | 69 | if let URLResults = dataDetectorService.extractLinks(fromTextBody: link) { 70 | for result in URLResults { 71 | print(result.source) 72 | print(result.data) 73 | print() 74 | } 75 | } 76 | 77 | print("\n-----Multiple Bodies URL Results-----\n") 78 | 79 | if let combinedURLResults = dataDetectorService.extractLinks(fromTextBodies: links) { 80 | for individualResults in combinedURLResults { 81 | for result in individualResults { 82 | print(result.source) 83 | print(result.data) 84 | print() 85 | } 86 | } 87 | } 88 | 89 | print("\n-----Single Body Phone Results-----\n") 90 | 91 | if let phoneResults = dataDetectorService.extractPhoneNumbers(fromTextBody: phone) { 92 | for result in phoneResults { 93 | print(result.source) 94 | print(result.data) 95 | print() 96 | } 97 | } 98 | 99 | print("\n-----Multiple Bodies Phone Results-----\n") 100 | 101 | if let combinedPhoneResults = dataDetectorService.extractPhoneNumbers(fromTextBodies: phones) { 102 | for individualResults in combinedPhoneResults { 103 | for result in individualResults { 104 | print(result.source) 105 | print(result.data) 106 | print() 107 | } 108 | } 109 | } 110 | 111 | print("\n-----Single Body Transit Results-----\n") 112 | 113 | if let transitResults = dataDetectorService.extractTransitInformation(fromTextBody: transit) { 114 | for result in transitResults { 115 | print(result.source) 116 | print(result.data) 117 | print() 118 | } 119 | } 120 | 121 | print("\n-----Multiple Bodies Transit Results-----\n") 122 | 123 | if let combinedTransitResults = dataDetectorService.extractTransitInformation(fromTextBodies: transits) { 124 | for individualResults in combinedTransitResults { 125 | for result in individualResults { 126 | print(result.source) 127 | print(result.data) 128 | print() 129 | } 130 | } 131 | } 132 | 133 | print("\n-----Basic Scenario Implementation Results-----\n") 134 | 135 | print(textBody.dates!) 136 | print(link.links!) 137 | print(transit.transitInfo!) 138 | print(address.addresses!) 139 | print(phone.phoneNumbers!) 140 | 141 | print("\n-----Mixed Request Results-----\n") 142 | 143 | let mixedText = "Call Mayank on +1123456789 on 2017/13/12 at 5pm when you reach 990 Belchertown Rd, Amherst, MA - 01440 USA." 144 | 145 | if let results = dataDetectorService.extractInformation(fromTextBody: mixedText, withResultTypes: .date, .address, .phoneNumber) { 146 | for result in results { 147 | switch result.dataType { 148 | case .date: 149 | let date = result.data as! Date 150 | print(date) 151 | break 152 | case .address: 153 | let address = result.data as! AddressInfo 154 | print(address) 155 | break 156 | case .phoneNumber: 157 | let phoneNumber = result.data as! String 158 | print(phoneNumber) 159 | break 160 | default: 161 | continue 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /TestGround-tvOS.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // Test below - delete tests before commits, or exclude this file in your commits 2 | import MKDataDetector 3 | import CoreLocation 4 | import UIKit 5 | 6 | let dataDetectorService = MKDataDetectorService() 7 | 8 | let textBody = "hello world at 9pm tomorrow" 9 | let textBodies = ["event on monday", "movie on wednesday", "payment due next thursday", "time", "money", "yellowstone park visit next friday", "party next friday at 8pm"] 10 | 11 | let address = "5123 Kere street, get here fast MA 01003, USA" 12 | let legalAddress = "133 Belchertown Rd Amherst MA - 01003 United States" 13 | let addresses = ["B713, Samartha Aangan II, Oshiwara, Andheri West, Mumbai - 400053, India", "133 Belchertown Rd Amherst MA - 01003 United States"] 14 | 15 | let link = "Mayank's Github: https://github.com/mayankk2308" 16 | let links = [link, "another link ://ssdsd", "testlink://", "www.apple.com", "http://apple.com"] 17 | 18 | let phone = "Mayank: +1 (413) 801 6324" 19 | let phones = ["+14138016324", "Mayank: +91 9920095040", "400053"] 20 | 21 | let transit = "UA 2392" 22 | let transits = ["Flight 2334", "EK 239 to Boston", "Emirates Airlines", "United Flight 2223"] 23 | 24 | print("-----Single Body Date Results-----\n") 25 | if let singleBodyDateResults = dataDetectorService.extractDates(fromTextBody: textBody) { 26 | for result in singleBodyDateResults { 27 | print(result.source) 28 | print(result.data) 29 | print() 30 | } 31 | } 32 | 33 | print("\n-----Multiple Bodies Date Results-----\n") 34 | 35 | if let combinedDateResults = dataDetectorService.extractDates(fromTextBodies: textBodies) { 36 | for individualResults in combinedDateResults { 37 | for result in individualResults { 38 | print(result.source) 39 | print(result.data) 40 | print() 41 | } 42 | } 43 | } 44 | 45 | print("\n-----Single Body Address Results-----\n") 46 | 47 | if let addressResults = dataDetectorService.extractAddresses(fromTextBody: address) { 48 | for result in addressResults { 49 | print(result.source) 50 | print(result.data) 51 | print() 52 | } 53 | } 54 | 55 | print("\n-----Multiple Bodies Address Results-----\n") 56 | 57 | if let combinedAddressResults = dataDetectorService.extractAddresses(fromTextBodies: addresses) { 58 | for individualResults in combinedAddressResults { 59 | for result in individualResults { 60 | print(result.source) 61 | print(result.data) 62 | print() 63 | } 64 | } 65 | } 66 | 67 | print("\n-----Single Body URL Results-----\n") 68 | 69 | if let URLResults = dataDetectorService.extractLinks(fromTextBody: link) { 70 | for result in URLResults { 71 | print(result.source) 72 | print(result.data) 73 | print() 74 | } 75 | } 76 | 77 | print("\n-----Multiple Bodies URL Results-----\n") 78 | 79 | if let combinedURLResults = dataDetectorService.extractLinks(fromTextBodies: links) { 80 | for individualResults in combinedURLResults { 81 | for result in individualResults { 82 | print(result.source) 83 | print(result.data) 84 | print() 85 | } 86 | } 87 | } 88 | 89 | print("\n-----Single Body Phone Results-----\n") 90 | 91 | if let phoneResults = dataDetectorService.extractPhoneNumbers(fromTextBody: phone) { 92 | for result in phoneResults { 93 | print(result.source) 94 | print(result.data) 95 | print() 96 | } 97 | } 98 | 99 | print("\n-----Multiple Bodies Phone Results-----\n") 100 | 101 | if let combinedPhoneResults = dataDetectorService.extractPhoneNumbers(fromTextBodies: phones) { 102 | for individualResults in combinedPhoneResults { 103 | for result in individualResults { 104 | print(result.source) 105 | print(result.data) 106 | print() 107 | } 108 | } 109 | } 110 | 111 | print("\n-----Single Body Transit Results-----\n") 112 | 113 | if let transitResults = dataDetectorService.extractTransitInformation(fromTextBody: transit) { 114 | for result in transitResults { 115 | print(result.source) 116 | print(result.data) 117 | print() 118 | } 119 | } 120 | 121 | print("\n-----Multiple Bodies Transit Results-----\n") 122 | 123 | if let combinedTransitResults = dataDetectorService.extractTransitInformation(fromTextBodies: transits) { 124 | for individualResults in combinedTransitResults { 125 | for result in individualResults { 126 | print(result.source) 127 | print(result.data) 128 | print() 129 | } 130 | } 131 | } 132 | 133 | print("\n-----Basic Scenario Implementation Results-----\n") 134 | 135 | print(textBody.dates!) 136 | print(link.links!) 137 | print(transit.transitInfo!) 138 | print(address.addresses!) 139 | print(phone.phoneNumbers!) 140 | 141 | print("\n-----Mixed Request Results-----\n") 142 | 143 | let mixedText = "Call Mayank on +1123456789 on 2017/13/12 at 5pm when you reach 990 Belchertown Rd, Amherst, MA - 01440 USA." 144 | 145 | if let results = dataDetectorService.extractInformation(fromTextBody: mixedText, withResultTypes: .date, .address, .phoneNumber) { 146 | for result in results { 147 | switch result.dataType { 148 | case .date: 149 | let date = result.data as! Date 150 | print(date) 151 | break 152 | case .address: 153 | let address = result.data as! AddressInfo 154 | print(address) 155 | break 156 | case .phoneNumber: 157 | let phoneNumber = result.data as! String 158 | print(phoneNumber) 159 | break 160 | default: 161 | continue 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /MKDataDetectorTests/AddressExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressExtensionTests.swift 3 | // MKDataDetector 4 | // 5 | // Created by Jeet Parte on 22/07/17. 6 | // Copyright 2017 Mayank Kumar. Available under the MIT license. 7 | // 8 | 9 | import XCTest 10 | import CoreLocation 11 | @testable import MKDataDetector 12 | 13 | class AddressTests: XCTestCase { 14 | 15 | let dataDetectorService = MKDataDetectorService() 16 | var textBody = "1800 Ellis St,San Francisco, CA 94102, United States" 17 | var textBodies = ["1800 Ellis St,San Francisco, CA 94102, United States", "133 Belchertown Rd Amherst MA - 01003 United States"] 18 | let type: NSTextCheckingResult.CheckingType = .address 19 | var nsDataDetector: NSDataDetector! 20 | 21 | override func setUp() { 22 | super.setUp() 23 | nsDataDetector = try! NSDataDetector(types: type.rawValue) 24 | } 25 | 26 | func testSingleTextBody() { 27 | let results = dataDetectorService.extractAddresses(fromTextBody: textBody) 28 | let expectedCount = nsDataDetector.numberOfMatches(in: textBody, range: NSMakeRange(0, textBody.utf16.count)) 29 | if expectedCount > 0 { 30 | XCTAssertNotNil(results) 31 | XCTAssertEqual(results!.count, expectedCount) 32 | } else { 33 | XCTAssertNil(results) 34 | } 35 | } 36 | 37 | func testMultipleTextBodies() { 38 | let combinedResults = dataDetectorService.extractAddresses(fromTextBodies: textBodies) 39 | 40 | //Check no.of matches for all textbodies 41 | let mergedTextBody = concatenate(textBodies) 42 | let totalMatches = nsDataDetector.numberOfMatches(in: mergedTextBody, range: NSMakeRange(0, mergedTextBody.utf16.count)) 43 | if totalMatches > 0 { 44 | XCTAssertNotNil(combinedResults) 45 | for individualResult in combinedResults! { 46 | XCTAssertFalse(individualResult.isEmpty) 47 | 48 | //Check no. of matches for a present textbody 49 | let source = individualResult.first!.source //grab the textbody 50 | let expectedCount = nsDataDetector.numberOfMatches(in: source, range: NSMakeRange(0, source.utf16.count)) 51 | XCTAssertEqual(individualResult.count, expectedCount) 52 | } 53 | } else { 54 | XCTAssertNil(combinedResults) 55 | } 56 | } 57 | 58 | // func testLocationExtractionFromAddressCompletionBlock() { 59 | // //Test whether completion block is being executed 60 | // var completionBlockExecuted = false 61 | // let validAddress = "1800 Ellis St, San Francisco, CA 94102, United States" 62 | // dataDetectorService.extractLocation(fromAddress: validAddress) { (location) in 63 | // do { 64 | // completionBlockExecuted = true 65 | // XCTAssert(completionBlockExecuted) 66 | // } 67 | // } 68 | // } 69 | 70 | func testLocationExtractionValidAddress() { 71 | //Test valid address 72 | let validAddress = "1800 Ellis St, San Francisco, CA 94102, United States" 73 | dataDetectorService.extractLocation(fromAddress: validAddress) { location in 74 | XCTAssertNotNil(location) 75 | XCTAssertEqual(location!.coordinate.latitude, 37.7858, accuracy: 0.05) 76 | XCTAssertEqual(location!.coordinate.longitude, -122.4065, accuracy: 0.05) 77 | } 78 | } 79 | 80 | func testLocationExtractionInvalidAddress() { 81 | //Test invalid address 82 | let invalidAddress = "No man's land 🌎" 83 | dataDetectorService.extractLocation(fromAddress: invalidAddress) { location in 84 | XCTAssertNil(location) 85 | } 86 | } 87 | 88 | 89 | func testLocationExtractionValidAddressResult() { 90 | //Test valid address 91 | let validAddress = "1800 Ellis St, San Francisco, CA 94102, United States" 92 | let results = dataDetectorService.extractAddresses(fromTextBody: validAddress) 93 | let result = results!.first 94 | dataDetectorService.extractLocation(fromAnalysisResult: (result!)) { location in 95 | XCTAssertNotNil(location) 96 | XCTAssertEqual(location!.coordinate.latitude, 37.7858, accuracy: 0.05) 97 | XCTAssertEqual(location!.coordinate.longitude, -122.4065, accuracy: 0.05) 98 | } 99 | } 100 | 101 | func testLocationExtractionInvalidAddressResult() { 102 | //Test invalid address 103 | let invalidAddress = "Fake address: 1800 Calvin St, San Francisco, CA 94102, United Kingdom" 104 | let results = dataDetectorService.extractAddresses(fromTextBody: invalidAddress) 105 | let result = results!.first 106 | dataDetectorService.extractLocation(fromAnalysisResult: (result!)) { location in 107 | XCTAssertNil(location) 108 | } 109 | } 110 | 111 | func testLocationExtractionPlaceMark() { 112 | //placemark nil, error nil 113 | let location = dataDetectorService.extractLocation(fromPlacemarks: nil, withError: nil) 114 | XCTAssertNil(location) 115 | 116 | //placemark nil, error not nil 117 | let error = CLError(_nsError: NSError()) 118 | let locationII = dataDetectorService.extractLocation(fromPlacemarks: nil, withError: error) 119 | XCTAssertNil(locationII) 120 | 121 | //placemark not nil, error nil 122 | let geocoder = CLGeocoder() 123 | let validAddress = "1800 Ellis St, San Francisco, CA 94102, United States" 124 | geocoder.geocodeAddressString(validAddress) { (placemarks, error) in 125 | XCTAssertNil(error) 126 | let locationIII = self.dataDetectorService.extractLocation(fromPlacemarks: placemarks, withError: nil) 127 | XCTAssertNotNil(locationIII) 128 | } 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Header](/Resources/hero.png) 2 | # **MKDataDetector** 3 | [![Build Status](https://travis-ci.org/mayankk2308/mkdatadetector.svg?branch=master)](https://travis-ci.org/mayankk2308/mkdatadetector) 4 | ![Platform badge](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS-blue.svg) 5 | 6 | A simple convenience wrapper in Swift for data detection from natural language text that organizes data extraction and handling. 7 | 8 | ## Purpose 9 | 10 | `MKDataDetector` streamlines `NSDataDetector` and builds on it with additional supporting capabilities that use information effectively. 11 | 12 | ## Requirements 13 | 14 | * Swift 3.1+ 15 | * macOS 10.9+ 16 | * iOS 8.0+ 17 | * tvOS 9.0+ 18 | * watchOS 2.0+ 19 | 20 | ## Installation 21 | 22 | There are multiple installation options to choose from. 23 | 24 | ### CocoaPods (Preferred) 25 | 26 | To install via CocoaPods, add the following line to your **Podfile**: 27 | ```ruby 28 | pod 'MKDataDetector' 29 | ``` 30 | 31 | ### Carthage 32 | 33 | To install via Carthage, add the following line to your **Cartfile** along with the specific version of the framework you might prefer: 34 | ``` 35 | github "mayankk2308/mkdatadetector-swift" 36 | ``` 37 | 38 | ### Manual 39 | 40 | 1. Create a submodule in your project directory: `git submodule add https://github.com/mayankk2308/mkdatadetector-swift.git` 41 | 2. Open the submodule directory and drag the **.xcodeproj** file into your project. 42 | 3. Add **MKDataDetector.framework** to your target's _Link Binary with Libraries_ **Build Phase**. 43 | 4. You can now use the framework by importing it. 44 | 45 | ## Usage 46 | 47 | `MKDataDetectorService` is packaged as a set of extensions that compartmentalize its following capabilities: 48 | 49 | * Date - date extraction 50 | * Address - address extraction 51 | * Link - link extraction 52 | * Phone Number - phone number detection 53 | * Transit Information - flight information extraction, etc. 54 | 55 | In addition to extracting these features, the framework also provides convenience functions to manipulate and organize this data. 56 | 57 | To import and use the `MKDataDetectorService` class, add the following statement to your `.swift` file: 58 | ```swift 59 | import MKDataDetector 60 | ``` 61 | 62 | You can use basic functionality as an extension of `String`: 63 | ```swift 64 | let testString: String = "sampleText" 65 | 66 | // extract Dates 67 | if let dates: [Date] = testString.dates { 68 | print(dates) 69 | } 70 | 71 | // extract Links 72 | if let links: [URL] = testString.links { 73 | print(links) 74 | } 75 | ``` 76 | 77 | Similar extensions exist for addresses, transit, and phone numbers as well. For more informative results, you may want to initialize the service. 78 | 79 | ### Initialization 80 | 81 | You can declare an instance as follows: 82 | ```swift 83 | let dataDetectorService: MKDataDetectorService = MKDataDetectorService() 84 | ``` 85 | 86 | ### Result Handling 87 | 88 | A generic set of `AnalysisResult` structures is consistently returned for extraction/analysis results. An enumeration called `ResultType` is also included for identification of results. 89 | 90 | `AnalysisResult` contains **5** fields: 91 | * Source (`source`) - the source/original complete `String` from which data was detected 92 | * Match Range (`rangeInSource`) - the `NSRange` of the matched string in the original string 93 | * Data String (`dataString`) - the substring from which `data` was matched 94 | * Data Type (`dataType`) - the type `ResultType` of data returned 95 | * Data (`data`) - the data `T` extracted from the source input 96 | 97 | The generic struct has a `typealias` per result type: 98 | * `DateAnalysisResult` - for `AnalysisResult` 99 | * `URLAnalysisResult` - for `AnalysisResult` 100 | * `AddressAnalysisResult` - for `AnalysisResult` 101 | * `PhoneNumberAnalysisResult` - for `AnalysisResult` 102 | * `TransitAnalysisResult` - for `AnalysisResult` 103 | 104 | The address and transit information results are structures (`[String : String]`) typealiased as `AddressInfo` and `TransitInfo`. `Address` and `Transit` **_structs_** with their associated dictionary keys make information lookup simple. For example, to access the zip-code in an extracted address, simply use the key `Address.zip`. 105 | 106 | ### Implementation 107 | 108 | To extract dates from some text (`String`): 109 | ```swift 110 | if let results = dataDetectorService.extractDates(withTextBody: sampleTextBody) { 111 | for result in results { 112 | print(result.source) 113 | print(result.data) 114 | // do some stuff 115 | } 116 | } 117 | ``` 118 | For a given `textBody`, the `dataDetectorService` returns an array of `DateAnalysisResult` objects. 119 | 120 | To extract dates from multiple sources of text (`[String]`): 121 | ```swift 122 | if let combinedResults = dataDetectorService.extractDates(withTextBodies: [sampleText, sampleText, ...]) { 123 | for individualResults in combinedResults { 124 | for result in individualResults { 125 | print(result.source) 126 | print(result.data) 127 | // do some stuff 128 | } 129 | } 130 | } 131 | ``` 132 | For given `textBodies`, the `dataDetectorService` returns an array of `[DateAnalysisResult]` objects. 133 | 134 | The extraction process is uniform for other types of data features such as phone numbers, addresses, links, and more. 135 | 136 | In cases where detection with multiple `ResultType`s are required, the following implementation may be used: 137 | ```swift 138 | if let results = dataDetectorService.extractInformation(fromTextBody textBody: String, withResultTypes: .date, .address ...) { 139 | for result in results { 140 | switch result.dataType { 141 | case .date: 142 | // force cast as DateAnalysisResult - create and save events, etc. 143 | case .address: 144 | // force cast as AddressAnalysisResult - get location, etc. 145 | . 146 | . 147 | // for all result types you are concerned with, i.e, your input parameters 148 | } 149 | } 150 | } 151 | ``` 152 | 153 | An implementation for extracting multiple types from multiple text bodies is also included. 154 | 155 | ### Additional Capabilities 156 | 157 | `MKDataDetector` also provides handy convenience functions to use detected information. 158 | 159 | To retrieve precise **location information** from a **valid** `String` address: 160 | ```swift 161 | dataDetectorService.extractLocation(fromAddress: sampleText) { location in 162 | if extractedLocation = location { 163 | // CLLocation object available, requires importing 'CoreLocation' 164 | } 165 | } 166 | ``` 167 | 168 | Alternatively, if you already have an `AddressAnalysisResult`: 169 | ```swift 170 | dataDetectorService.extractLocation(fromAnalysisResult: sampleAnalysisResult) { location in 171 | if extractedLocation = location { 172 | // CLLocation object available, requires importing 'CoreLocation' 173 | } 174 | } 175 | ``` 176 | 177 | For **calendar integration**, you can easily create an `EKEvent` from a `DateAnalysisResult`: 178 | ```swift 179 | let event: EKEvent = dataDetectorService.generateEvent(fromEventStore: sampleEventStore, withAnalysisResult: sampleResult) 180 | ``` 181 | A __*withEndDate*__ parameter, not shown above, is optional. Not providing a value defaults the event to end after an hour. 182 | 183 | A more generic event generator is also available, which may be preferred if 100% event naming consistency is expected. Automatic processing of event names from `DateAnalysisResult` objects needs more testing. 184 | 185 | Given a set of retrieved `AnalysisResult` (the default result type for any extraction operation), you can generate **colored attributed texts**: 186 | ```swift 187 | if let attributedText = dataDetectorService.attributedText(fromAnalysisResults: sampleResults, withColor: UIColor.blue.cgcolor) { 188 | // set UI component 189 | } 190 | ``` 191 | 192 | More convenience capabilities will be incorporated into future releases. 193 | 194 | ### Examples 195 | 196 | Consider the following inputs: 197 | ```swift 198 | let meeting: String = "Meeting at 9pm tomorrow" 199 | let party: String = "Party next Friday at 8pm" 200 | ``` 201 | 202 | Extracting dates using `dataDetectorService`, we receive the following output for `meeting`: 203 | * `source` = *"Meeting at 9pm tomorrow"* 204 | * `sourceInRange` = `NSRange` of the match *"at 9pm tomorrow"* 205 | * `dataString` = the match substring *"at 9pm tomorrow"* 206 | * `data` = equivalent `Date` object, specifying source date relative to the current date/time on the device 207 | 208 | The output is similar for `party`: 209 | * `source` = *"Party next Friday at 8pm"* 210 | * `sourceInRange` = `NSRange` of the match *"next Friday at 8pm"* 211 | * `dataString` = the match substring *"next Friday at 8pm"* 212 | * `data` = equivalent `Date` object, specifying source date relative to the current date/time on the device 213 | 214 | The output format will be uniform for other types of data features as well, with the `data` field returning objects of the appropriate type in each case. 215 | 216 | You can easily make use of this data, for instance, by generating an event: 217 | ```swift 218 | let meetingEvent = dataDetectorService.generateEvent(withEventStore: someEventStore, withAnalysisResult: meetingAnalysisResult) 219 | // creates an event detailing a meeting for 9pm tomorrow, lasting an hour 220 | 221 | let partyEvent = dataDetectorService.generateEvent(withEventStore: someEventStore, withAnalysisResult: partyAnalysisResult) 222 | // creates an event detailing a party at 8pm next Friday, lasting an hour 223 | ``` 224 | 225 | Assume that the `meeting` text was embedded in a `UILabel` called `meetingLabel`. It was also expanded to add *" and next Friday at 5pm"*. You can update the label to display the **multiple detected** parts of the text: 226 | ```swift 227 | if let attributedText = dataDetectorService.attributedText(withAnalysisResults: meetingAnalysisResult, withColor: UIColor.purple.cgcolor) { 228 | meetingLabel.attributedText = attributedText 229 | } 230 | ``` 231 | 232 | `meetingLabel` will now display the original text with the detected information in purple (bold here): 233 | *"Meeting __at 9pm tomorrow__ and __next Friday at 5pm__"*. 234 | 235 | ### Limitations 236 | 237 | Apple's documentation on `NSDataDetector` states that the class can currently match dates, addresses, links, phone numbers and transit information, and not other present properties such as grammar and spelling. 238 | 239 | Additionally, `NSDataDetector` does not detect: 240 | * the **name**, **job title**, **organization** & **phone number** components of an address, although keys for the same are provided within the original API 241 | * the **airline name** component for transit information, although a key for this is available in the original API 242 | 243 | ## Contact 244 | 245 | You can contact: 246 | * [Mayank Kumar](https://github.com/mayankk2308) - via [email](mailto:mayankk2308@gmail.com) or [LinkedIn](https://www.linkedin.com/in/mayank-kumar-478245b1/) 247 | * [Jeet Parte](https://github.com/jeetparte) - via [email](mailto:jeetparte@gmail.com) 248 | 249 | for any inquires or community-related issues. 250 | 251 | ## License 252 | 253 | This project is available under the [MIT](https://github.com/mayankk2308/mkdatadetector-swift/blob/master/LICENSE.md) license. 254 | -------------------------------------------------------------------------------- /MKDataDetector.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A8574ABE1F1B4EB900493423 /* StringAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8574ABD1F1B4EB900493423 /* StringAccess.swift */; }; 11 | A85D37F11F13677600F5DDF1 /* AnalysisResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85D37F01F13677600F5DDF1 /* AnalysisResult.swift */; }; 12 | A874CD001F10F3260022AA85 /* MKDataDetector.h in Headers */ = {isa = PBXBuildFile; fileRef = A874CCFE1F10F3260022AA85 /* MKDataDetector.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | A874CD0A1F11011E0022AA85 /* MKDataDetectorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A874CD091F11011E0022AA85 /* MKDataDetectorService.swift */; }; 14 | A887490B1F6C538500013F25 /* MKDataDetector.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A874CCFB1F10F3260022AA85 /* MKDataDetector.framework */; }; 15 | A88749111F6C53D500013F25 /* MultiDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C954BEB51F248A950030D00B /* MultiDataTests.swift */; }; 16 | A88749121F6C53D500013F25 /* StringAccessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C954BEB31F248A850030D00B /* StringAccessTests.swift */; }; 17 | A88749131F6C53D500013F25 /* TransitInformationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C954BEB11F2488C80030D00B /* TransitInformationExtensionTests.swift */; }; 18 | A88749141F6C53D500013F25 /* PhoneNumberExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C954BEAF1F2487DD0030D00B /* PhoneNumberExtensionTests.swift */; }; 19 | A88749151F6C53D500013F25 /* LinkExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94DDE261F23A1B80049D156 /* LinkExtensionTests.swift */; }; 20 | A88749161F6C53D500013F25 /* AddressExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94DDE241F23A1010049D156 /* AddressExtensionTests.swift */; }; 21 | A88749171F6C53D500013F25 /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94DDE221F239E8F0049D156 /* DateExtensionTests.swift */; }; 22 | A88749181F6C53D500013F25 /* TestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94DDE281F23B03F0049D156 /* TestUtilities.swift */; }; 23 | A88DD8331F123B8A001D067A /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88DD8321F123B8A001D067A /* Date.swift */; }; 24 | A88DD8371F123BA7001D067A /* Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88DD8361F123BA7001D067A /* Address.swift */; }; 25 | A88DD83B1F123E83001D067A /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88DD83A1F123E83001D067A /* PhoneNumber.swift */; }; 26 | A88DD8411F12403C001D067A /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88DD8401F12403C001D067A /* Link.swift */; }; 27 | A88DD8451F12407C001D067A /* TransitInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88DD8441F12407C001D067A /* TransitInformation.swift */; }; 28 | A8A061A41F1FCB3400EF542E /* AliasManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A061A31F1FCB3400EF542E /* AliasManager.swift */; }; 29 | A8B869171F1D138C003FC75A /* MultiData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B869161F1D138C003FC75A /* MultiData.swift */; }; 30 | A8D560201F139F3E00553632 /* UtilityExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D5601F1F139F3E00553632 /* UtilityExtensions.swift */; }; 31 | A8EC757C1F14D67400D15369 /* CoreKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8EC757B1F14D67400D15369 /* CoreKeys.swift */; }; 32 | A8F07E341F14724700E1A5C5 /* ResultType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F07E331F14724700E1A5C5 /* ResultType.swift */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | A887490C1F6C538500013F25 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = A874CCF21F10F3260022AA85 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = A874CCFA1F10F3260022AA85; 41 | remoteInfo = MKDataDetector; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | A8574ABD1F1B4EB900493423 /* StringAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringAccess.swift; sourceTree = ""; }; 47 | A85D37F01F13677600F5DDF1 /* AnalysisResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalysisResult.swift; sourceTree = ""; }; 48 | A874CCFB1F10F3260022AA85 /* MKDataDetector.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MKDataDetector.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | A874CCFE1F10F3260022AA85 /* MKDataDetector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKDataDetector.h; sourceTree = ""; }; 50 | A874CCFF1F10F3260022AA85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | A874CD091F11011E0022AA85 /* MKDataDetectorService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MKDataDetectorService.swift; sourceTree = ""; }; 52 | A88749061F6C538500013F25 /* MKDataDetectorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MKDataDetectorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | A887490A1F6C538500013F25 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | A88DD8321F123B8A001D067A /* Date.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 55 | A88DD8361F123BA7001D067A /* Address.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Address.swift; sourceTree = ""; }; 56 | A88DD83A1F123E83001D067A /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; 57 | A88DD8401F12403C001D067A /* Link.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Link.swift; sourceTree = ""; }; 58 | A88DD8441F12407C001D067A /* TransitInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitInformation.swift; sourceTree = ""; }; 59 | A8A061A31F1FCB3400EF542E /* AliasManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AliasManager.swift; sourceTree = ""; }; 60 | A8B869161F1D138C003FC75A /* MultiData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiData.swift; sourceTree = ""; }; 61 | A8D5601F1F139F3E00553632 /* UtilityExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityExtensions.swift; sourceTree = ""; }; 62 | A8EC757B1F14D67400D15369 /* CoreKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreKeys.swift; sourceTree = ""; }; 63 | A8F07E331F14724700E1A5C5 /* ResultType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultType.swift; sourceTree = ""; }; 64 | C94DDE221F239E8F0049D156 /* DateExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; 65 | C94DDE241F23A1010049D156 /* AddressExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressExtensionTests.swift; sourceTree = ""; }; 66 | C94DDE261F23A1B80049D156 /* LinkExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkExtensionTests.swift; sourceTree = ""; }; 67 | C94DDE281F23B03F0049D156 /* TestUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtilities.swift; sourceTree = ""; }; 68 | C954BEAF1F2487DD0030D00B /* PhoneNumberExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumberExtensionTests.swift; sourceTree = ""; }; 69 | C954BEB11F2488C80030D00B /* TransitInformationExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitInformationExtensionTests.swift; sourceTree = ""; }; 70 | C954BEB31F248A850030D00B /* StringAccessTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringAccessTests.swift; sourceTree = ""; }; 71 | C954BEB51F248A950030D00B /* MultiDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiDataTests.swift; sourceTree = ""; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | A874CCF71F10F3260022AA85 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | ); 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | A88749031F6C538500013F25 /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | A887490B1F6C538500013F25 /* MKDataDetector.framework in Frameworks */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | A874CCF11F10F3260022AA85 = { 94 | isa = PBXGroup; 95 | children = ( 96 | A874CCFD1F10F3260022AA85 /* MKDataDetector */, 97 | A895A6F41F1A358E006A6C35 /* MKDataDetectorTests */, 98 | A874CCFC1F10F3260022AA85 /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | A874CCFC1F10F3260022AA85 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | A874CCFB1F10F3260022AA85 /* MKDataDetector.framework */, 106 | A88749061F6C538500013F25 /* MKDataDetectorTests.xctest */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | A874CCFD1F10F3260022AA85 /* MKDataDetector */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | A88DD8381F123BB3001D067A /* Core Services */, 115 | A8EC75791F14D56900D15369 /* Data Management */, 116 | A88DD8391F123BC7001D067A /* Extensions */, 117 | A874CD061F10F3370022AA85 /* Configuration */, 118 | ); 119 | path = MKDataDetector; 120 | sourceTree = ""; 121 | }; 122 | A874CD061F10F3370022AA85 /* Configuration */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | A874CCFE1F10F3260022AA85 /* MKDataDetector.h */, 126 | A874CCFF1F10F3260022AA85 /* Info.plist */, 127 | ); 128 | name = Configuration; 129 | sourceTree = ""; 130 | }; 131 | A88DD8381F123BB3001D067A /* Core Services */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | A874CD091F11011E0022AA85 /* MKDataDetectorService.swift */, 135 | A8D5601F1F139F3E00553632 /* UtilityExtensions.swift */, 136 | ); 137 | name = "Core Services"; 138 | sourceTree = ""; 139 | }; 140 | A88DD8391F123BC7001D067A /* Extensions */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | A88DD8321F123B8A001D067A /* Date.swift */, 144 | A88DD8361F123BA7001D067A /* Address.swift */, 145 | A88DD8401F12403C001D067A /* Link.swift */, 146 | A88DD83A1F123E83001D067A /* PhoneNumber.swift */, 147 | A88DD8441F12407C001D067A /* TransitInformation.swift */, 148 | A8574ABD1F1B4EB900493423 /* StringAccess.swift */, 149 | A8B869161F1D138C003FC75A /* MultiData.swift */, 150 | ); 151 | name = Extensions; 152 | sourceTree = ""; 153 | }; 154 | A895A6F41F1A358E006A6C35 /* MKDataDetectorTests */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | C954BEB51F248A950030D00B /* MultiDataTests.swift */, 158 | C954BEB31F248A850030D00B /* StringAccessTests.swift */, 159 | C954BEB11F2488C80030D00B /* TransitInformationExtensionTests.swift */, 160 | C954BEAF1F2487DD0030D00B /* PhoneNumberExtensionTests.swift */, 161 | C94DDE261F23A1B80049D156 /* LinkExtensionTests.swift */, 162 | C94DDE241F23A1010049D156 /* AddressExtensionTests.swift */, 163 | C94DDE221F239E8F0049D156 /* DateExtensionTests.swift */, 164 | C94DDE281F23B03F0049D156 /* TestUtilities.swift */, 165 | A887490A1F6C538500013F25 /* Info.plist */, 166 | ); 167 | path = MKDataDetectorTests; 168 | sourceTree = ""; 169 | }; 170 | A8EC75791F14D56900D15369 /* Data Management */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | A85D37F01F13677600F5DDF1 /* AnalysisResult.swift */, 174 | A8A061A31F1FCB3400EF542E /* AliasManager.swift */, 175 | A8F07E331F14724700E1A5C5 /* ResultType.swift */, 176 | A8EC757B1F14D67400D15369 /* CoreKeys.swift */, 177 | ); 178 | name = "Data Management"; 179 | sourceTree = ""; 180 | }; 181 | /* End PBXGroup section */ 182 | 183 | /* Begin PBXHeadersBuildPhase section */ 184 | A874CCF81F10F3260022AA85 /* Headers */ = { 185 | isa = PBXHeadersBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | A874CD001F10F3260022AA85 /* MKDataDetector.h in Headers */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXHeadersBuildPhase section */ 193 | 194 | /* Begin PBXNativeTarget section */ 195 | A874CCFA1F10F3260022AA85 /* MKDataDetector */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = A874CD031F10F3260022AA85 /* Build configuration list for PBXNativeTarget "MKDataDetector" */; 198 | buildPhases = ( 199 | A874CCF61F10F3260022AA85 /* Sources */, 200 | A874CCF71F10F3260022AA85 /* Frameworks */, 201 | A874CCF81F10F3260022AA85 /* Headers */, 202 | A874CCF91F10F3260022AA85 /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | ); 208 | name = MKDataDetector; 209 | productName = MKDataDetector; 210 | productReference = A874CCFB1F10F3260022AA85 /* MKDataDetector.framework */; 211 | productType = "com.apple.product-type.framework"; 212 | }; 213 | A88749051F6C538500013F25 /* MKDataDetectorTests */ = { 214 | isa = PBXNativeTarget; 215 | buildConfigurationList = A887490E1F6C538500013F25 /* Build configuration list for PBXNativeTarget "MKDataDetectorTests" */; 216 | buildPhases = ( 217 | A88749021F6C538500013F25 /* Sources */, 218 | A88749031F6C538500013F25 /* Frameworks */, 219 | A88749041F6C538500013F25 /* Resources */, 220 | ); 221 | buildRules = ( 222 | ); 223 | dependencies = ( 224 | A887490D1F6C538500013F25 /* PBXTargetDependency */, 225 | ); 226 | name = MKDataDetectorTests; 227 | productName = MKDataDetectorTests; 228 | productReference = A88749061F6C538500013F25 /* MKDataDetectorTests.xctest */; 229 | productType = "com.apple.product-type.bundle.unit-test"; 230 | }; 231 | /* End PBXNativeTarget section */ 232 | 233 | /* Begin PBXProject section */ 234 | A874CCF21F10F3260022AA85 /* Project object */ = { 235 | isa = PBXProject; 236 | attributes = { 237 | LastSwiftUpdateCheck = 0900; 238 | LastUpgradeCheck = 1100; 239 | ORGANIZATIONNAME = "Mayank Kumar"; 240 | TargetAttributes = { 241 | A874CCFA1F10F3260022AA85 = { 242 | CreatedOnToolsVersion = 8.3.3; 243 | DevelopmentTeam = YF7XP6ZYSQ; 244 | LastSwiftMigration = 1100; 245 | ProvisioningStyle = Automatic; 246 | }; 247 | A88749051F6C538500013F25 = { 248 | CreatedOnToolsVersion = 9.0; 249 | DevelopmentTeam = YF7XP6ZYSQ; 250 | LastSwiftMigration = 1100; 251 | ProvisioningStyle = Automatic; 252 | }; 253 | }; 254 | }; 255 | buildConfigurationList = A874CCF51F10F3260022AA85 /* Build configuration list for PBXProject "MKDataDetector" */; 256 | compatibilityVersion = "Xcode 3.2"; 257 | developmentRegion = en; 258 | hasScannedForEncodings = 0; 259 | knownRegions = ( 260 | en, 261 | Base, 262 | ); 263 | mainGroup = A874CCF11F10F3260022AA85; 264 | productRefGroup = A874CCFC1F10F3260022AA85 /* Products */; 265 | projectDirPath = ""; 266 | projectRoot = ""; 267 | targets = ( 268 | A874CCFA1F10F3260022AA85 /* MKDataDetector */, 269 | A88749051F6C538500013F25 /* MKDataDetectorTests */, 270 | ); 271 | }; 272 | /* End PBXProject section */ 273 | 274 | /* Begin PBXResourcesBuildPhase section */ 275 | A874CCF91F10F3260022AA85 /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | A88749041F6C538500013F25 /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXResourcesBuildPhase section */ 290 | 291 | /* Begin PBXSourcesBuildPhase section */ 292 | A874CCF61F10F3260022AA85 /* Sources */ = { 293 | isa = PBXSourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | A88DD8331F123B8A001D067A /* Date.swift in Sources */, 297 | A8574ABE1F1B4EB900493423 /* StringAccess.swift in Sources */, 298 | A874CD0A1F11011E0022AA85 /* MKDataDetectorService.swift in Sources */, 299 | A8A061A41F1FCB3400EF542E /* AliasManager.swift in Sources */, 300 | A88DD83B1F123E83001D067A /* PhoneNumber.swift in Sources */, 301 | A88DD8371F123BA7001D067A /* Address.swift in Sources */, 302 | A88DD8451F12407C001D067A /* TransitInformation.swift in Sources */, 303 | A88DD8411F12403C001D067A /* Link.swift in Sources */, 304 | A8B869171F1D138C003FC75A /* MultiData.swift in Sources */, 305 | A85D37F11F13677600F5DDF1 /* AnalysisResult.swift in Sources */, 306 | A8D560201F139F3E00553632 /* UtilityExtensions.swift in Sources */, 307 | A8EC757C1F14D67400D15369 /* CoreKeys.swift in Sources */, 308 | A8F07E341F14724700E1A5C5 /* ResultType.swift in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | A88749021F6C538500013F25 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | A88749161F6C53D500013F25 /* AddressExtensionTests.swift in Sources */, 317 | A88749151F6C53D500013F25 /* LinkExtensionTests.swift in Sources */, 318 | A88749181F6C53D500013F25 /* TestUtilities.swift in Sources */, 319 | A88749121F6C53D500013F25 /* StringAccessTests.swift in Sources */, 320 | A88749171F6C53D500013F25 /* DateExtensionTests.swift in Sources */, 321 | A88749141F6C53D500013F25 /* PhoneNumberExtensionTests.swift in Sources */, 322 | A88749111F6C53D500013F25 /* MultiDataTests.swift in Sources */, 323 | A88749131F6C53D500013F25 /* TransitInformationExtensionTests.swift in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXTargetDependency section */ 330 | A887490D1F6C538500013F25 /* PBXTargetDependency */ = { 331 | isa = PBXTargetDependency; 332 | target = A874CCFA1F10F3260022AA85 /* MKDataDetector */; 333 | targetProxy = A887490C1F6C538500013F25 /* PBXContainerItemProxy */; 334 | }; 335 | /* End PBXTargetDependency section */ 336 | 337 | /* Begin XCBuildConfiguration section */ 338 | A874CD011F10F3260022AA85 /* Debug */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ALWAYS_SEARCH_USER_PATHS = NO; 342 | ARCHS = "$(ARCHS_STANDARD)"; 343 | CLANG_ANALYZER_NONNULL = YES; 344 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 356 | CLANG_WARN_EMPTY_BODY = YES; 357 | CLANG_WARN_ENUM_CONVERSION = YES; 358 | CLANG_WARN_INFINITE_RECURSION = YES; 359 | CLANG_WARN_INT_CONVERSION = YES; 360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 365 | CLANG_WARN_STRICT_PROTOTYPES = YES; 366 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 367 | CLANG_WARN_UNREACHABLE_CODE = YES; 368 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 369 | "CODE_SIGN_IDENTITY[sdk=*]" = "iPhone Developer"; 370 | COPY_PHASE_STRIP = NO; 371 | CURRENT_PROJECT_VERSION = 1; 372 | DEBUG_INFORMATION_FORMAT = dwarf; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | ENABLE_TESTABILITY = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_DYNAMIC_NO_PIC = NO; 377 | GCC_NO_COMMON_BLOCKS = YES; 378 | GCC_OPTIMIZATION_LEVEL = 0; 379 | GCC_PREPROCESSOR_DEFINITIONS = ( 380 | "DEBUG=1", 381 | "$(inherited)", 382 | ); 383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 385 | GCC_WARN_UNDECLARED_SELECTOR = YES; 386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 387 | GCC_WARN_UNUSED_FUNCTION = YES; 388 | GCC_WARN_UNUSED_VARIABLE = YES; 389 | INFOPLIST_FILE = Source/Info.plist; 390 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 391 | MACOSX_DEPLOYMENT_TARGET = 10.9; 392 | MTL_ENABLE_DEBUG_INFO = YES; 393 | ONLY_ACTIVE_ARCH = YES; 394 | PRODUCT_NAME = MKDataDetector; 395 | SDKROOT = ""; 396 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvsimulator appletvos watchsimulator watchos"; 397 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 399 | SWIFT_VERSION = 3.0; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | TVOS_DEPLOYMENT_TARGET = 9.0; 402 | VERSIONING_SYSTEM = "apple-generic"; 403 | VERSION_INFO_PREFIX = ""; 404 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 405 | }; 406 | name = Debug; 407 | }; 408 | A874CD021F10F3260022AA85 /* Release */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | ALWAYS_SEARCH_USER_PATHS = NO; 412 | ARCHS = "$(ARCHS_STANDARD)"; 413 | CLANG_ANALYZER_NONNULL = YES; 414 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 416 | CLANG_CXX_LIBRARY = "libc++"; 417 | CLANG_ENABLE_MODULES = YES; 418 | CLANG_ENABLE_OBJC_ARC = YES; 419 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_COMMA = YES; 422 | CLANG_WARN_CONSTANT_CONVERSION = YES; 423 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 424 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 425 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 426 | CLANG_WARN_EMPTY_BODY = YES; 427 | CLANG_WARN_ENUM_CONVERSION = YES; 428 | CLANG_WARN_INFINITE_RECURSION = YES; 429 | CLANG_WARN_INT_CONVERSION = YES; 430 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 431 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 432 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 434 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 435 | CLANG_WARN_STRICT_PROTOTYPES = YES; 436 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 437 | CLANG_WARN_UNREACHABLE_CODE = YES; 438 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 439 | "CODE_SIGN_IDENTITY[sdk=*]" = "iPhone Developer"; 440 | COPY_PHASE_STRIP = NO; 441 | CURRENT_PROJECT_VERSION = 1; 442 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | ENABLE_STRICT_OBJC_MSGSEND = YES; 445 | GCC_C_LANGUAGE_STANDARD = gnu99; 446 | GCC_NO_COMMON_BLOCKS = YES; 447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 449 | GCC_WARN_UNDECLARED_SELECTOR = YES; 450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 451 | GCC_WARN_UNUSED_FUNCTION = YES; 452 | GCC_WARN_UNUSED_VARIABLE = YES; 453 | INFOPLIST_FILE = Source/Info.plist; 454 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 455 | MACOSX_DEPLOYMENT_TARGET = 10.9; 456 | MTL_ENABLE_DEBUG_INFO = NO; 457 | PRODUCT_NAME = MKDataDetector; 458 | SDKROOT = ""; 459 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvsimulator appletvos watchsimulator watchos"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 461 | SWIFT_VERSION = 3.0; 462 | TARGETED_DEVICE_FAMILY = "1,2"; 463 | TVOS_DEPLOYMENT_TARGET = 9.0; 464 | VALIDATE_PRODUCT = YES; 465 | VERSIONING_SYSTEM = "apple-generic"; 466 | VERSION_INFO_PREFIX = ""; 467 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 468 | }; 469 | name = Release; 470 | }; 471 | A874CD041F10F3260022AA85 /* Debug */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | APPLICATION_EXTENSION_API_ONLY = YES; 475 | CLANG_ANALYZER_GCD_PERFORMANCE = YES; 476 | CLANG_ENABLE_MODULES = YES; 477 | CLANG_STATIC_ANALYZER_MODE = deep; 478 | CODE_SIGN_IDENTITY = ""; 479 | CODE_SIGN_STYLE = Automatic; 480 | DEFINES_MODULE = YES; 481 | DEVELOPMENT_TEAM = YF7XP6ZYSQ; 482 | DYLIB_COMPATIBILITY_VERSION = 1; 483 | DYLIB_CURRENT_VERSION = 1; 484 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 485 | INFOPLIST_FILE = MKDataDetector/Info.plist; 486 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 487 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 488 | MARKETING_VERSION = 2.0.3; 489 | PRODUCT_BUNDLE_IDENTIFIER = com.mayank.MKDataDetector; 490 | PRODUCT_NAME = "$(TARGET_NAME)"; 491 | PROVISIONING_PROFILE_SPECIFIER = ""; 492 | RUN_CLANG_STATIC_ANALYZER = YES; 493 | SKIP_INSTALL = YES; 494 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 495 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 496 | SWIFT_VERSION = 5.0; 497 | TARGETED_DEVICE_FAMILY = "1,2,3,4"; 498 | }; 499 | name = Debug; 500 | }; 501 | A874CD051F10F3260022AA85 /* Release */ = { 502 | isa = XCBuildConfiguration; 503 | buildSettings = { 504 | APPLICATION_EXTENSION_API_ONLY = YES; 505 | CLANG_ANALYZER_GCD_PERFORMANCE = YES; 506 | CLANG_ENABLE_MODULES = YES; 507 | CLANG_STATIC_ANALYZER_MODE = deep; 508 | CODE_SIGN_IDENTITY = ""; 509 | CODE_SIGN_STYLE = Automatic; 510 | DEFINES_MODULE = YES; 511 | DEVELOPMENT_TEAM = YF7XP6ZYSQ; 512 | DYLIB_COMPATIBILITY_VERSION = 1; 513 | DYLIB_CURRENT_VERSION = 1; 514 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 515 | INFOPLIST_FILE = MKDataDetector/Info.plist; 516 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 517 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 518 | MARKETING_VERSION = 2.0.3; 519 | PRODUCT_BUNDLE_IDENTIFIER = com.mayank.MKDataDetector; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | PROVISIONING_PROFILE_SPECIFIER = ""; 522 | RUN_CLANG_STATIC_ANALYZER = YES; 523 | SKIP_INSTALL = YES; 524 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 525 | SWIFT_VERSION = 5.0; 526 | TARGETED_DEVICE_FAMILY = "1,2,3,4"; 527 | }; 528 | name = Release; 529 | }; 530 | A887490F1F6C538500013F25 /* Debug */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 534 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 535 | CODE_SIGN_IDENTITY = "Mac Developer"; 536 | CODE_SIGN_STYLE = Automatic; 537 | COMBINE_HIDPI_IMAGES = YES; 538 | DEVELOPMENT_TEAM = YF7XP6ZYSQ; 539 | GCC_C_LANGUAGE_STANDARD = gnu11; 540 | INFOPLIST_FILE = MKDataDetectorTests/Info.plist; 541 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 542 | MACOSX_DEPLOYMENT_TARGET = 10.13; 543 | PRODUCT_BUNDLE_IDENTIFIER = com.mayank.MKDataDetectorTests; 544 | PRODUCT_NAME = "$(TARGET_NAME)"; 545 | PROVISIONING_PROFILE_SPECIFIER = ""; 546 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; 547 | SWIFT_VERSION = 5.0; 548 | TARGETED_DEVICE_FAMILY = "1,2,3"; 549 | }; 550 | name = Debug; 551 | }; 552 | A88749101F6C538500013F25 /* Release */ = { 553 | isa = XCBuildConfiguration; 554 | buildSettings = { 555 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 556 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 557 | CODE_SIGN_IDENTITY = "Mac Developer"; 558 | CODE_SIGN_STYLE = Automatic; 559 | COMBINE_HIDPI_IMAGES = YES; 560 | DEVELOPMENT_TEAM = YF7XP6ZYSQ; 561 | GCC_C_LANGUAGE_STANDARD = gnu11; 562 | INFOPLIST_FILE = MKDataDetectorTests/Info.plist; 563 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 564 | MACOSX_DEPLOYMENT_TARGET = 10.13; 565 | PRODUCT_BUNDLE_IDENTIFIER = com.mayank.MKDataDetectorTests; 566 | PRODUCT_NAME = "$(TARGET_NAME)"; 567 | PROVISIONING_PROFILE_SPECIFIER = ""; 568 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; 569 | SWIFT_VERSION = 5.0; 570 | TARGETED_DEVICE_FAMILY = "1,2,3"; 571 | }; 572 | name = Release; 573 | }; 574 | /* End XCBuildConfiguration section */ 575 | 576 | /* Begin XCConfigurationList section */ 577 | A874CCF51F10F3260022AA85 /* Build configuration list for PBXProject "MKDataDetector" */ = { 578 | isa = XCConfigurationList; 579 | buildConfigurations = ( 580 | A874CD011F10F3260022AA85 /* Debug */, 581 | A874CD021F10F3260022AA85 /* Release */, 582 | ); 583 | defaultConfigurationIsVisible = 0; 584 | defaultConfigurationName = Release; 585 | }; 586 | A874CD031F10F3260022AA85 /* Build configuration list for PBXNativeTarget "MKDataDetector" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | A874CD041F10F3260022AA85 /* Debug */, 590 | A874CD051F10F3260022AA85 /* Release */, 591 | ); 592 | defaultConfigurationIsVisible = 0; 593 | defaultConfigurationName = Release; 594 | }; 595 | A887490E1F6C538500013F25 /* Build configuration list for PBXNativeTarget "MKDataDetectorTests" */ = { 596 | isa = XCConfigurationList; 597 | buildConfigurations = ( 598 | A887490F1F6C538500013F25 /* Debug */, 599 | A88749101F6C538500013F25 /* Release */, 600 | ); 601 | defaultConfigurationIsVisible = 0; 602 | defaultConfigurationName = Release; 603 | }; 604 | /* End XCConfigurationList section */ 605 | }; 606 | rootObject = A874CCF21F10F3260022AA85 /* Project object */; 607 | } 608 | --------------------------------------------------------------------------------