├── GitHubImages
├── hashVideo.gif
└── UITextViewStoryboard.png
├── textViewSample.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── robertchen.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── textViewSample.xccheckout
├── xcuserdata
│ └── robertchen.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── textViewSample.xcscheme
└── project.pbxproj
├── textViewSample
├── TextCell.swift
├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── UITextField+Extension.swift
└── ViewController.swift
├── textViewSampleTests
├── Info.plist
└── textViewSampleTests.swift
└── README.md
/GitHubImages/hashVideo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/SwiftTextViewHashtag/HEAD/GitHubImages/hashVideo.gif
--------------------------------------------------------------------------------
/GitHubImages/UITextViewStoryboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/SwiftTextViewHashtag/HEAD/GitHubImages/UITextViewStoryboard.png
--------------------------------------------------------------------------------
/textViewSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/textViewSample.xcodeproj/project.xcworkspace/xcuserdata/robertchen.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/SwiftTextViewHashtag/HEAD/textViewSample.xcodeproj/project.xcworkspace/xcuserdata/robertchen.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/textViewSample/TextCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextCell.swift
3 | // textViewSample
4 | //
5 | // Created by Robert Chen on 5/8/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TextCell : UITableViewCell {
12 |
13 | @IBOutlet weak var textView: UITextView!
14 |
15 | func setCellText(text:String){
16 | textView.text = text
17 | textView.resolveHashTags()
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/textViewSample.xcodeproj/xcuserdata/robertchen.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | textViewSample.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | FC6AF2831AFD59A7003BAE37
16 |
17 | primary
18 |
19 |
20 | FC6AF2981AFD59A7003BAE37
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/textViewSampleTests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/textViewSampleTests/textViewSampleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // textViewSampleTests.swift
3 | // textViewSampleTests
4 | //
5 | // Created by Robert Chen on 5/8/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | class textViewSampleTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | XCTAssert(true, "Pass")
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measureBlock() {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/textViewSample/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/textViewSample/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/textViewSample.xcodeproj/project.xcworkspace/xcshareddata/textViewSample.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | 2290921F-A0C7-4874-98C2-95AF7C777D30
9 | IDESourceControlProjectName
10 | textViewSample
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 2F057DB66CD94F58C2DFE665E1E7F44057186C1C
14 | https://github.com/ThornTechPublic/SwiftTextViewHashtag.git
15 |
16 | IDESourceControlProjectPath
17 | textViewSample.xcodeproj
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 2F057DB66CD94F58C2DFE665E1E7F44057186C1C
21 | ../..
22 |
23 | IDESourceControlProjectURL
24 | https://github.com/ThornTechPublic/SwiftTextViewHashtag.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 2F057DB66CD94F58C2DFE665E1E7F44057186C1C
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 2F057DB66CD94F58C2DFE665E1E7F44057186C1C
36 | IDESourceControlWCCName
37 | SwiftTextViewHashtag
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/textViewSample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // textViewSample
4 | //
5 | // Created by Robert Chen on 5/8/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/textViewSample/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftTextViewHashtag
2 | Hash tag detection for TextViews in Swift
3 |
4 | ## Overview
5 |
6 | This is a quick and dirty sample project implementing Hashtag detection
7 |
8 | 
9 |
10 | ## Installation / Integration
11 |
12 | To use this in your own app, just copy the [UITextField+Extension.swift](https://github.com/ThornTechPublic/SwiftTextViewHashtag/blob/master/textViewSample/UITextField%2BExtension.swift) file into your project.
13 |
14 | You may want to further [customize this section of code](https://github.com/ThornTechPublic/SwiftTextViewHashtag/blob/master/textViewSample/UITextField%2BExtension.swift#L27) with a TextView font and color that fits your app's style guide, because the Storyboard attributes get overridden.
15 |
16 | ## Usage
17 |
18 | To detect hashtags in your textViews:
19 |
20 | 1. Make sure the UITextView is `selectable`, can detect `links`, and is not `editable` (see image below)
21 | 1. Wire the UITextView delegate to your ViewController
22 | 1. Implement the UITextViewDelegate method `textView:shouldInteractWithURL:` to hook into the URL tap. [See example](https://github.com/ThornTechPublic/SwiftTextViewHashtag/blob/master/textViewSample/ViewController.swift#L164).
23 | 1. After you set the text, call the `resolveHashTags()` method. [See example](https://github.com/ThornTechPublic/SwiftTextViewHashtag/blob/master/textViewSample/TextCell.swift#L17)
24 |
25 | 
26 |
27 | ## How it works
28 |
29 | The approach used here is to add an attribute (much like an "href") to hashtagged words.
30 |
31 | ### URL detection
32 |
33 | Hashtag detection builds upon URL detection. This is why the TextView is setup to detect links. Note that links are only clickable when `Editable` is unchecked.
34 |
35 | At this point, URLs in the textview will open in the Safari app.
36 |
37 | ### Add link attributes
38 |
39 | Next, an "href" attribute is added to each hashtagged word.
40 |
41 | The overall process goes something like this:
42 | * Iterate over each word and look for anything that starts with `#`
43 | * Chop off the first character `#`. For example, `#helloWorld` becomes `helloWorld`
44 | * Create a fake URL using a fake URL scheme. For example, `hash:helloWorld`
45 | * Associate this fake URL with the hashtag word. `NSMutableAttributedString` has APIs to accomplish this.
46 |
47 | [Here's the code](https://github.com/ribl/SwiftTextViewHashtag/blob/master/textViewSample/UITextField%2BExtension.swift#L13)
48 |
49 | ### Clicking on hashtags
50 |
51 | Now that hashtags are URLs, they are a different color and can be clicked. Note: it's tempting to add a tap gesture to TextView, but you can leverage the built-in delegate instead.
52 |
53 | Intercept the URL click and check for your fake URL scheme.
54 | * Set the TextView delegate.
55 | * Implement the `UITextFieldDelegate` which has a `shouldInteractWithURL` method
56 | * Check for your fake `URL.scheme`
57 | * Grab the payload in the `URL.resourceSpecifier`
58 |
59 | [Here's the code](https://github.com/ThornTechPublic/SwiftTextViewHashtag/blob/master/textViewSample/ViewController.swift#L164)
60 |
61 | ## Other resources
62 |
63 | * [STTweetLabel](https://github.com/SebastienThiebaud/STTweetLabel), an Objective-C CocoaPod for hashtag detection
64 | * A [swift implementation](https://yeti.co/blog/hashtags-and-mentions/) of hashtags and mentions. I wish this was available when I first implemented hashtags. Their approach is slightly different though.
65 | * I used [this](http://kishikawakatsumi.hatenablog.com/entry/20130605/1370370925) to initially figure out my approach. You might need to click "translate from japanese" on the top.
66 | * I used [this](http://stackoverflow.com/questions/11547919/check-if-string-contains-a-hashtag-and-then-change-hashtag-color) to figure out how to use `NSMutableAttributedString`
67 | * [Ray Wenderlich Scroll View](http://www.raywenderlich.com/video-tutorials#swiftscrollview) video series helped me understand keyboard movement in the example project.
68 |
--------------------------------------------------------------------------------
/textViewSample/UITextField+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITextField+Extension.swift
3 | // textViewSample
4 | //
5 | // Created by Robert Chen on 5/22/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | func += ( left: inout Dictionary, right: Dictionary) {
12 | for (k, v) in right {
13 | left.updateValue(v, forKey: k)
14 | }
15 | }
16 |
17 | extension String {
18 | func NSRangeFromRange(range: Range) -> NSRange {
19 | let utf16view = self.utf16
20 | let from = String.UTF16View.Index(range.lowerBound, within: utf16view)
21 | let to = String.UTF16View.Index(range.upperBound, within: utf16view)
22 | return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from),
23 | utf16view.distance(from: from, to: to))
24 | }
25 |
26 | mutating func dropTrailingNonAlphaNumericCharacters() {
27 | let nonAlphaNumericCharacters = NSCharacterSet.alphanumerics.inverted
28 | let characterArray = components(separatedBy: nonAlphaNumericCharacters)
29 | if let first = characterArray.first {
30 | self = first
31 | }
32 | }
33 | }
34 |
35 | extension UITextView {
36 |
37 | public func resolveHashTags(possibleUserDisplayNames:[String]? = nil) {
38 |
39 | let schemeMap = [
40 | "#":"hash",
41 | "@":"mention"
42 | ]
43 |
44 | // Separate the string into individual words.
45 | // Whitespace is used as the word boundary.
46 | // You might see word boundaries at special characters, like before a period.
47 | // But we need to be careful to retain the # or @ characters.
48 | let words = self.text.components(separatedBy: NSCharacterSet.whitespacesAndNewlines)
49 | let attributedString = attributedText.mutableCopy() as! NSMutableAttributedString
50 |
51 | // keep track of where we are as we interate through the string.
52 | // otherwise, a string like "#test #test" will only highlight the first one.
53 | var bookmark = text.startIndex
54 |
55 | // Iterate over each word.
56 | // So far each word will look like:
57 | // - I
58 | // - visited
59 | // - #123abc.go!
60 | // The last word is a hashtag of #123abc
61 | // Use the following hashtag rules:
62 | // - Include the hashtag # in the URL
63 | // - Only include alphanumeric characters. Special chars and anything after are chopped off.
64 | // - Hashtags can start with numbers. But the whole thing can't be a number (#123abc is ok, #123 is not)
65 | for word in words {
66 |
67 | var scheme:String? = nil
68 |
69 | if word.hasPrefix("#") {
70 | scheme = schemeMap["#"]
71 | } else if word.hasPrefix("@") {
72 | scheme = schemeMap["@"]
73 | }
74 |
75 | // Drop the # or @
76 | var wordWithTagRemoved = String(word.characters.dropFirst())
77 |
78 | // Drop any trailing punctuation
79 | wordWithTagRemoved.dropTrailingNonAlphaNumericCharacters()
80 |
81 | // Make sure we still have a valid word (i.e. not just '#' or '@' by itself, not #100)
82 | guard let schemeMatch = scheme, Int(wordWithTagRemoved) == nil && !wordWithTagRemoved.isEmpty
83 | else { continue }
84 |
85 | let remainingRange = Range(bookmark..
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
75 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
94 |
96 |
102 |
103 |
104 |
105 |
107 |
108 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/textViewSample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // textViewSample
4 | //
5 | // Created by Robert Chen on 5/8/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | var lastTextViewHeight:CGFloat = 0.0
14 |
15 | var messages:[String] = ["regular text, nothing to see here", "#ribl <- click on it to see an alert", "@riblapp mention tags work too", "regular urls are clickable http://ribl.co", "add your own text below"]
16 |
17 | @IBOutlet weak var tableView: UITableView!
18 | @IBOutlet weak var toolbarBottom: NSLayoutConstraint!
19 | @IBOutlet weak var textView: UITextView!
20 | @IBOutlet weak var placeholderText: UILabel!
21 | @IBOutlet weak var textviewHeight: NSLayoutConstraint!
22 |
23 | @IBAction func sendButton(sender: AnyObject) {
24 | textView.endEditing(true)
25 | messages.append(textView.text)
26 | // clear the text
27 | textView.text = ""
28 | // and manually trigger the delegate method
29 | self.textViewDidChange(textView)
30 | tableView.reloadData()
31 | }
32 |
33 | override func viewDidLoad() {
34 | super.viewDidLoad()
35 | // listen for the keyboard going up and down
36 | keyboardHeightRegisterNotifications()
37 | }
38 |
39 | }
40 |
41 | // MARK: - Keyboard helper methods
42 |
43 | // all this just to move the keyboard up and down.
44 | extension ViewController {
45 |
46 | /// register keyboard notifications to shift the scrollview content insets
47 | func keyboardHeightRegisterNotifications() {
48 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
49 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
50 | }
51 |
52 | /// just pass true or false if you're shifting the keyboard up or down
53 | func keyboardWillShow(notification: NSNotification) {
54 | adjustInsetForKeyboardShow(true, notification: notification)
55 | }
56 |
57 | func keyboardWillHide(notification: NSNotification) {
58 | adjustInsetForKeyboardShow(false, notification: notification)
59 | }
60 |
61 | /// consolidate the keyboard movement logic into one method, and just pass a boolean for up or down.
62 | func adjustInsetForKeyboardShow(show: Bool, notification: NSNotification) {
63 | // some implementations out there use -1 and 1 to move it up or down.
64 | // debugging is a little easier if you use -1 and 0 instead.
65 | toolbarBottom.constant = getKeyboardHeight(notification) * (show ? 1 : 0)
66 | // normally, the constraint change is updated immediately.
67 | // by simply added UIView.animateWithDuration along with a layoutIfNeeded(),
68 | // the constraint change will happen in the animation.
69 | // the animation settings below sort of match the keyboard animation
70 | UIView.animateWithDuration(0.5,
71 | delay: 0.0,
72 | options: .CurveEaseInOut,
73 | animations: {
74 | // animate the constraint change
75 | self.view.layoutIfNeeded()
76 | },
77 | completion: nil
78 | )
79 | }
80 |
81 | func getKeyboardHeight(notification: NSNotification) -> CGFloat{
82 | // == userInfo || {}
83 | let userInfo = notification.userInfo ?? [:]
84 | // CGRect wrapped in a NSValue
85 | // Make sure you use UIKeyboardFrameEndUserInfoKey, NOT UIKeyboardFrameBeginUserInfoKey
86 | // "End" is good. "Begin" is bad.
87 | // To test, switch keyboards and make sure the heights are correct.
88 | let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
89 | return CGRectGetHeight(keyboardFrame)
90 | }
91 |
92 | }
93 |
94 | // MARK: - UITableViewDataSource methods
95 |
96 | extension ViewController : UITableViewDataSource {
97 |
98 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
99 | return messages.count
100 | }
101 |
102 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
103 | let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! TextCell
104 | cell.setCellText(messages[indexPath.row])
105 | return cell
106 | }
107 |
108 | }
109 |
110 | // MARK: - UITableViewDelegate methods
111 |
112 | extension ViewController : UITableViewDelegate {
113 |
114 | func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
115 | // set the height of the row based on the text height.
116 | // TODO: there's probably a better way to do this.
117 |
118 | // type cast to NSString to get additional methods
119 | let myString: NSString = messages[indexPath.row] as NSString
120 | // label height depends on font
121 | let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(18.0)]
122 | let stringWidth:CGFloat = UIScreen.mainScreen().bounds.width - 20
123 | // hard-coding a really big number that exceeds the screen height
124 | let infiniteHeight:CGFloat = 1600
125 | let temporarySize = CGSizeMake(stringWidth, infiniteHeight)
126 | let rect:CGRect = myString.boundingRectWithSize(temporarySize, options: .UsesLineFragmentOrigin, attributes: attributes, context: nil)
127 | return rect.height + 30
128 | }
129 |
130 | }
131 |
132 | // MARK: - UITextViewDelegate methods
133 |
134 | extension ViewController : UITextViewDelegate {
135 |
136 | // increase the height of the textview as the user types
137 | func textViewDidChange(textView: UITextView){
138 | // hide placeholder text
139 | placeholderText.hidden = !textView.text.isEmpty
140 | // create a hypothetical tall box that contains the text.
141 | // then shrink down the height based on the content.
142 | let newSize:CGSize = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: 1600.0))
143 | // remember that new height
144 | let newHeight = newSize.height
145 | // change the height constraint only if it's different.
146 | // otherwise, it get set on every single character the user types.
147 | if lastTextViewHeight != newHeight {
148 | lastTextViewHeight = newHeight
149 | // the 7.0 is to account for the top of the text getting scrolled up slightly
150 | // to account for a potential new line
151 | textviewHeight.constant = newSize.height + 7.0
152 | }
153 | }
154 |
155 | func showHashTagAlert(tagType:String, payload:String){
156 | let alertView = UIAlertView()
157 | alertView.title = "\(tagType) tag detected"
158 | // get a handle on the payload
159 | alertView.message = "\(payload)"
160 | alertView.addButtonWithTitle("Ok")
161 | alertView.show()
162 | }
163 |
164 | func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
165 | // check for our fake URL scheme hash:helloWorld
166 | switch URL.scheme {
167 | case "hash" :
168 | showHashTagAlert("hash", payload: URL.resourceSpecifier.stringByRemovingPercentEncoding!)
169 | case "mention" :
170 | showHashTagAlert("mention", payload: URL.resourceSpecifier.stringByRemovingPercentEncoding!)
171 | default:
172 | print("just a regular url")
173 | }
174 |
175 | return true
176 | }
177 |
178 | }
--------------------------------------------------------------------------------
/textViewSample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
91 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/textViewSample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FC32ED3A1B0FB23100FFBFF6 /* UITextField+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC32ED391B0FB23100FFBFF6 /* UITextField+Extension.swift */; };
11 | FC6AF28A1AFD59A7003BAE37 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC6AF2891AFD59A7003BAE37 /* AppDelegate.swift */; };
12 | FC6AF28C1AFD59A7003BAE37 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC6AF28B1AFD59A7003BAE37 /* ViewController.swift */; };
13 | FC6AF28F1AFD59A7003BAE37 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC6AF28D1AFD59A7003BAE37 /* Main.storyboard */; };
14 | FC6AF2911AFD59A7003BAE37 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FC6AF2901AFD59A7003BAE37 /* Images.xcassets */; };
15 | FC6AF2941AFD59A7003BAE37 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FC6AF2921AFD59A7003BAE37 /* LaunchScreen.xib */; };
16 | FC6AF2A01AFD59A7003BAE37 /* textViewSampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC6AF29F1AFD59A7003BAE37 /* textViewSampleTests.swift */; };
17 | FC6AF2AA1AFD66EE003BAE37 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC6AF2A91AFD66EE003BAE37 /* TextCell.swift */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXContainerItemProxy section */
21 | FC6AF29A1AFD59A7003BAE37 /* PBXContainerItemProxy */ = {
22 | isa = PBXContainerItemProxy;
23 | containerPortal = FC6AF27C1AFD59A7003BAE37 /* Project object */;
24 | proxyType = 1;
25 | remoteGlobalIDString = FC6AF2831AFD59A7003BAE37;
26 | remoteInfo = textViewSample;
27 | };
28 | /* End PBXContainerItemProxy section */
29 |
30 | /* Begin PBXFileReference section */
31 | FC32ED391B0FB23100FFBFF6 /* UITextField+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Extension.swift"; sourceTree = ""; };
32 | FC6AF2841AFD59A7003BAE37 /* textViewSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = textViewSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
33 | FC6AF2881AFD59A7003BAE37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
34 | FC6AF2891AFD59A7003BAE37 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
35 | FC6AF28B1AFD59A7003BAE37 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
36 | FC6AF28E1AFD59A7003BAE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
37 | FC6AF2901AFD59A7003BAE37 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
38 | FC6AF2931AFD59A7003BAE37 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
39 | FC6AF2991AFD59A7003BAE37 /* textViewSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = textViewSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
40 | FC6AF29E1AFD59A7003BAE37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | FC6AF29F1AFD59A7003BAE37 /* textViewSampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = textViewSampleTests.swift; sourceTree = ""; };
42 | FC6AF2A91AFD66EE003BAE37 /* TextCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = ""; };
43 | /* End PBXFileReference section */
44 |
45 | /* Begin PBXFrameworksBuildPhase section */
46 | FC6AF2811AFD59A7003BAE37 /* Frameworks */ = {
47 | isa = PBXFrameworksBuildPhase;
48 | buildActionMask = 2147483647;
49 | files = (
50 | );
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | FC6AF2961AFD59A7003BAE37 /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | );
58 | runOnlyForDeploymentPostprocessing = 0;
59 | };
60 | /* End PBXFrameworksBuildPhase section */
61 |
62 | /* Begin PBXGroup section */
63 | FC6AF27B1AFD59A7003BAE37 = {
64 | isa = PBXGroup;
65 | children = (
66 | FC6AF2861AFD59A7003BAE37 /* textViewSample */,
67 | FC6AF29C1AFD59A7003BAE37 /* textViewSampleTests */,
68 | FC6AF2851AFD59A7003BAE37 /* Products */,
69 | );
70 | sourceTree = "";
71 | };
72 | FC6AF2851AFD59A7003BAE37 /* Products */ = {
73 | isa = PBXGroup;
74 | children = (
75 | FC6AF2841AFD59A7003BAE37 /* textViewSample.app */,
76 | FC6AF2991AFD59A7003BAE37 /* textViewSampleTests.xctest */,
77 | );
78 | name = Products;
79 | sourceTree = "";
80 | };
81 | FC6AF2861AFD59A7003BAE37 /* textViewSample */ = {
82 | isa = PBXGroup;
83 | children = (
84 | FC6AF2891AFD59A7003BAE37 /* AppDelegate.swift */,
85 | FC6AF28B1AFD59A7003BAE37 /* ViewController.swift */,
86 | FC6AF2A91AFD66EE003BAE37 /* TextCell.swift */,
87 | FC32ED391B0FB23100FFBFF6 /* UITextField+Extension.swift */,
88 | FC6AF28D1AFD59A7003BAE37 /* Main.storyboard */,
89 | FC6AF2901AFD59A7003BAE37 /* Images.xcassets */,
90 | FC6AF2921AFD59A7003BAE37 /* LaunchScreen.xib */,
91 | FC6AF2871AFD59A7003BAE37 /* Supporting Files */,
92 | );
93 | path = textViewSample;
94 | sourceTree = "";
95 | };
96 | FC6AF2871AFD59A7003BAE37 /* Supporting Files */ = {
97 | isa = PBXGroup;
98 | children = (
99 | FC6AF2881AFD59A7003BAE37 /* Info.plist */,
100 | );
101 | name = "Supporting Files";
102 | sourceTree = "";
103 | };
104 | FC6AF29C1AFD59A7003BAE37 /* textViewSampleTests */ = {
105 | isa = PBXGroup;
106 | children = (
107 | FC6AF29F1AFD59A7003BAE37 /* textViewSampleTests.swift */,
108 | FC6AF29D1AFD59A7003BAE37 /* Supporting Files */,
109 | );
110 | path = textViewSampleTests;
111 | sourceTree = "";
112 | };
113 | FC6AF29D1AFD59A7003BAE37 /* Supporting Files */ = {
114 | isa = PBXGroup;
115 | children = (
116 | FC6AF29E1AFD59A7003BAE37 /* Info.plist */,
117 | );
118 | name = "Supporting Files";
119 | sourceTree = "";
120 | };
121 | /* End PBXGroup section */
122 |
123 | /* Begin PBXNativeTarget section */
124 | FC6AF2831AFD59A7003BAE37 /* textViewSample */ = {
125 | isa = PBXNativeTarget;
126 | buildConfigurationList = FC6AF2A31AFD59A7003BAE37 /* Build configuration list for PBXNativeTarget "textViewSample" */;
127 | buildPhases = (
128 | FC6AF2801AFD59A7003BAE37 /* Sources */,
129 | FC6AF2811AFD59A7003BAE37 /* Frameworks */,
130 | FC6AF2821AFD59A7003BAE37 /* Resources */,
131 | );
132 | buildRules = (
133 | );
134 | dependencies = (
135 | );
136 | name = textViewSample;
137 | productName = textViewSample;
138 | productReference = FC6AF2841AFD59A7003BAE37 /* textViewSample.app */;
139 | productType = "com.apple.product-type.application";
140 | };
141 | FC6AF2981AFD59A7003BAE37 /* textViewSampleTests */ = {
142 | isa = PBXNativeTarget;
143 | buildConfigurationList = FC6AF2A61AFD59A7003BAE37 /* Build configuration list for PBXNativeTarget "textViewSampleTests" */;
144 | buildPhases = (
145 | FC6AF2951AFD59A7003BAE37 /* Sources */,
146 | FC6AF2961AFD59A7003BAE37 /* Frameworks */,
147 | FC6AF2971AFD59A7003BAE37 /* Resources */,
148 | );
149 | buildRules = (
150 | );
151 | dependencies = (
152 | FC6AF29B1AFD59A7003BAE37 /* PBXTargetDependency */,
153 | );
154 | name = textViewSampleTests;
155 | productName = textViewSampleTests;
156 | productReference = FC6AF2991AFD59A7003BAE37 /* textViewSampleTests.xctest */;
157 | productType = "com.apple.product-type.bundle.unit-test";
158 | };
159 | /* End PBXNativeTarget section */
160 |
161 | /* Begin PBXProject section */
162 | FC6AF27C1AFD59A7003BAE37 /* Project object */ = {
163 | isa = PBXProject;
164 | attributes = {
165 | LastSwiftMigration = 0720;
166 | LastSwiftUpdateCheck = 0720;
167 | LastUpgradeCheck = 0730;
168 | ORGANIZATIONNAME = "Thorn Technologies";
169 | TargetAttributes = {
170 | FC6AF2831AFD59A7003BAE37 = {
171 | CreatedOnToolsVersion = 6.3.1;
172 | };
173 | FC6AF2981AFD59A7003BAE37 = {
174 | CreatedOnToolsVersion = 6.3.1;
175 | TestTargetID = FC6AF2831AFD59A7003BAE37;
176 | };
177 | };
178 | };
179 | buildConfigurationList = FC6AF27F1AFD59A7003BAE37 /* Build configuration list for PBXProject "textViewSample" */;
180 | compatibilityVersion = "Xcode 3.2";
181 | developmentRegion = English;
182 | hasScannedForEncodings = 0;
183 | knownRegions = (
184 | en,
185 | Base,
186 | );
187 | mainGroup = FC6AF27B1AFD59A7003BAE37;
188 | productRefGroup = FC6AF2851AFD59A7003BAE37 /* Products */;
189 | projectDirPath = "";
190 | projectRoot = "";
191 | targets = (
192 | FC6AF2831AFD59A7003BAE37 /* textViewSample */,
193 | FC6AF2981AFD59A7003BAE37 /* textViewSampleTests */,
194 | );
195 | };
196 | /* End PBXProject section */
197 |
198 | /* Begin PBXResourcesBuildPhase section */
199 | FC6AF2821AFD59A7003BAE37 /* Resources */ = {
200 | isa = PBXResourcesBuildPhase;
201 | buildActionMask = 2147483647;
202 | files = (
203 | FC6AF28F1AFD59A7003BAE37 /* Main.storyboard in Resources */,
204 | FC6AF2941AFD59A7003BAE37 /* LaunchScreen.xib in Resources */,
205 | FC6AF2911AFD59A7003BAE37 /* Images.xcassets in Resources */,
206 | );
207 | runOnlyForDeploymentPostprocessing = 0;
208 | };
209 | FC6AF2971AFD59A7003BAE37 /* Resources */ = {
210 | isa = PBXResourcesBuildPhase;
211 | buildActionMask = 2147483647;
212 | files = (
213 | );
214 | runOnlyForDeploymentPostprocessing = 0;
215 | };
216 | /* End PBXResourcesBuildPhase section */
217 |
218 | /* Begin PBXSourcesBuildPhase section */
219 | FC6AF2801AFD59A7003BAE37 /* Sources */ = {
220 | isa = PBXSourcesBuildPhase;
221 | buildActionMask = 2147483647;
222 | files = (
223 | FC6AF28C1AFD59A7003BAE37 /* ViewController.swift in Sources */,
224 | FC32ED3A1B0FB23100FFBFF6 /* UITextField+Extension.swift in Sources */,
225 | FC6AF2AA1AFD66EE003BAE37 /* TextCell.swift in Sources */,
226 | FC6AF28A1AFD59A7003BAE37 /* AppDelegate.swift in Sources */,
227 | );
228 | runOnlyForDeploymentPostprocessing = 0;
229 | };
230 | FC6AF2951AFD59A7003BAE37 /* Sources */ = {
231 | isa = PBXSourcesBuildPhase;
232 | buildActionMask = 2147483647;
233 | files = (
234 | FC6AF2A01AFD59A7003BAE37 /* textViewSampleTests.swift in Sources */,
235 | );
236 | runOnlyForDeploymentPostprocessing = 0;
237 | };
238 | /* End PBXSourcesBuildPhase section */
239 |
240 | /* Begin PBXTargetDependency section */
241 | FC6AF29B1AFD59A7003BAE37 /* PBXTargetDependency */ = {
242 | isa = PBXTargetDependency;
243 | target = FC6AF2831AFD59A7003BAE37 /* textViewSample */;
244 | targetProxy = FC6AF29A1AFD59A7003BAE37 /* PBXContainerItemProxy */;
245 | };
246 | /* End PBXTargetDependency section */
247 |
248 | /* Begin PBXVariantGroup section */
249 | FC6AF28D1AFD59A7003BAE37 /* Main.storyboard */ = {
250 | isa = PBXVariantGroup;
251 | children = (
252 | FC6AF28E1AFD59A7003BAE37 /* Base */,
253 | );
254 | name = Main.storyboard;
255 | sourceTree = "";
256 | };
257 | FC6AF2921AFD59A7003BAE37 /* LaunchScreen.xib */ = {
258 | isa = PBXVariantGroup;
259 | children = (
260 | FC6AF2931AFD59A7003BAE37 /* Base */,
261 | );
262 | name = LaunchScreen.xib;
263 | sourceTree = "";
264 | };
265 | /* End PBXVariantGroup section */
266 |
267 | /* Begin XCBuildConfiguration section */
268 | FC6AF2A11AFD59A7003BAE37 /* Debug */ = {
269 | isa = XCBuildConfiguration;
270 | buildSettings = {
271 | ALWAYS_SEARCH_USER_PATHS = NO;
272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
273 | CLANG_CXX_LIBRARY = "libc++";
274 | CLANG_ENABLE_MODULES = YES;
275 | CLANG_ENABLE_OBJC_ARC = YES;
276 | CLANG_WARN_BOOL_CONVERSION = YES;
277 | CLANG_WARN_CONSTANT_CONVERSION = YES;
278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
279 | CLANG_WARN_EMPTY_BODY = YES;
280 | CLANG_WARN_ENUM_CONVERSION = YES;
281 | CLANG_WARN_INT_CONVERSION = YES;
282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
283 | CLANG_WARN_UNREACHABLE_CODE = YES;
284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
285 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
286 | COPY_PHASE_STRIP = NO;
287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
288 | ENABLE_STRICT_OBJC_MSGSEND = YES;
289 | ENABLE_TESTABILITY = YES;
290 | GCC_C_LANGUAGE_STANDARD = gnu99;
291 | GCC_DYNAMIC_NO_PIC = NO;
292 | GCC_NO_COMMON_BLOCKS = YES;
293 | GCC_OPTIMIZATION_LEVEL = 0;
294 | GCC_PREPROCESSOR_DEFINITIONS = (
295 | "DEBUG=1",
296 | "$(inherited)",
297 | );
298 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
301 | GCC_WARN_UNDECLARED_SELECTOR = YES;
302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
303 | GCC_WARN_UNUSED_FUNCTION = YES;
304 | GCC_WARN_UNUSED_VARIABLE = YES;
305 | IPHONEOS_DEPLOYMENT_TARGET = 8.3;
306 | MTL_ENABLE_DEBUG_INFO = YES;
307 | ONLY_ACTIVE_ARCH = YES;
308 | SDKROOT = iphoneos;
309 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
310 | TARGETED_DEVICE_FAMILY = "1,2";
311 | };
312 | name = Debug;
313 | };
314 | FC6AF2A21AFD59A7003BAE37 /* Release */ = {
315 | isa = XCBuildConfiguration;
316 | buildSettings = {
317 | ALWAYS_SEARCH_USER_PATHS = NO;
318 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
319 | CLANG_CXX_LIBRARY = "libc++";
320 | CLANG_ENABLE_MODULES = YES;
321 | CLANG_ENABLE_OBJC_ARC = YES;
322 | CLANG_WARN_BOOL_CONVERSION = YES;
323 | CLANG_WARN_CONSTANT_CONVERSION = YES;
324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
325 | CLANG_WARN_EMPTY_BODY = YES;
326 | CLANG_WARN_ENUM_CONVERSION = YES;
327 | CLANG_WARN_INT_CONVERSION = YES;
328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
329 | CLANG_WARN_UNREACHABLE_CODE = YES;
330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
331 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
332 | COPY_PHASE_STRIP = NO;
333 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
334 | ENABLE_NS_ASSERTIONS = NO;
335 | ENABLE_STRICT_OBJC_MSGSEND = YES;
336 | GCC_C_LANGUAGE_STANDARD = gnu99;
337 | GCC_NO_COMMON_BLOCKS = YES;
338 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
339 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
340 | GCC_WARN_UNDECLARED_SELECTOR = YES;
341 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
342 | GCC_WARN_UNUSED_FUNCTION = YES;
343 | GCC_WARN_UNUSED_VARIABLE = YES;
344 | IPHONEOS_DEPLOYMENT_TARGET = 8.3;
345 | MTL_ENABLE_DEBUG_INFO = NO;
346 | SDKROOT = iphoneos;
347 | TARGETED_DEVICE_FAMILY = "1,2";
348 | VALIDATE_PRODUCT = YES;
349 | };
350 | name = Release;
351 | };
352 | FC6AF2A41AFD59A7003BAE37 /* Debug */ = {
353 | isa = XCBuildConfiguration;
354 | buildSettings = {
355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
356 | INFOPLIST_FILE = textViewSample/Info.plist;
357 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
358 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
359 | PRODUCT_NAME = "$(TARGET_NAME)";
360 | };
361 | name = Debug;
362 | };
363 | FC6AF2A51AFD59A7003BAE37 /* Release */ = {
364 | isa = XCBuildConfiguration;
365 | buildSettings = {
366 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
367 | INFOPLIST_FILE = textViewSample/Info.plist;
368 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
369 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
370 | PRODUCT_NAME = "$(TARGET_NAME)";
371 | };
372 | name = Release;
373 | };
374 | FC6AF2A71AFD59A7003BAE37 /* Debug */ = {
375 | isa = XCBuildConfiguration;
376 | buildSettings = {
377 | BUNDLE_LOADER = "$(TEST_HOST)";
378 | FRAMEWORK_SEARCH_PATHS = (
379 | "$(SDKROOT)/Developer/Library/Frameworks",
380 | "$(inherited)",
381 | );
382 | GCC_PREPROCESSOR_DEFINITIONS = (
383 | "DEBUG=1",
384 | "$(inherited)",
385 | );
386 | INFOPLIST_FILE = textViewSampleTests/Info.plist;
387 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
388 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
389 | PRODUCT_NAME = "$(TARGET_NAME)";
390 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/textViewSample.app/textViewSample";
391 | };
392 | name = Debug;
393 | };
394 | FC6AF2A81AFD59A7003BAE37 /* Release */ = {
395 | isa = XCBuildConfiguration;
396 | buildSettings = {
397 | BUNDLE_LOADER = "$(TEST_HOST)";
398 | FRAMEWORK_SEARCH_PATHS = (
399 | "$(SDKROOT)/Developer/Library/Frameworks",
400 | "$(inherited)",
401 | );
402 | INFOPLIST_FILE = textViewSampleTests/Info.plist;
403 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
404 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
405 | PRODUCT_NAME = "$(TARGET_NAME)";
406 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/textViewSample.app/textViewSample";
407 | };
408 | name = Release;
409 | };
410 | /* End XCBuildConfiguration section */
411 |
412 | /* Begin XCConfigurationList section */
413 | FC6AF27F1AFD59A7003BAE37 /* Build configuration list for PBXProject "textViewSample" */ = {
414 | isa = XCConfigurationList;
415 | buildConfigurations = (
416 | FC6AF2A11AFD59A7003BAE37 /* Debug */,
417 | FC6AF2A21AFD59A7003BAE37 /* Release */,
418 | );
419 | defaultConfigurationIsVisible = 0;
420 | defaultConfigurationName = Release;
421 | };
422 | FC6AF2A31AFD59A7003BAE37 /* Build configuration list for PBXNativeTarget "textViewSample" */ = {
423 | isa = XCConfigurationList;
424 | buildConfigurations = (
425 | FC6AF2A41AFD59A7003BAE37 /* Debug */,
426 | FC6AF2A51AFD59A7003BAE37 /* Release */,
427 | );
428 | defaultConfigurationIsVisible = 0;
429 | defaultConfigurationName = Release;
430 | };
431 | FC6AF2A61AFD59A7003BAE37 /* Build configuration list for PBXNativeTarget "textViewSampleTests" */ = {
432 | isa = XCConfigurationList;
433 | buildConfigurations = (
434 | FC6AF2A71AFD59A7003BAE37 /* Debug */,
435 | FC6AF2A81AFD59A7003BAE37 /* Release */,
436 | );
437 | defaultConfigurationIsVisible = 0;
438 | defaultConfigurationName = Release;
439 | };
440 | /* End XCConfigurationList section */
441 | };
442 | rootObject = FC6AF27C1AFD59A7003BAE37 /* Project object */;
443 | }
444 |
--------------------------------------------------------------------------------