diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..2bf1c1c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.3.1 diff --git a/.travis.yml b/.travis.yml index 10907bf..4c734a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode7.2 +osx_image: xcode7.3 env: matrix: - VERSION=8.4 @@ -16,7 +16,7 @@ install: before_script: - xcodebuild -workspace ICInputAccessory.xcworkspace -list script: - - bundle exec rake ci:test[$VERSION] + - bundle exec rake ci:build[$VERSION] notifications: email: false slack: diff --git a/CHANGELOG.md b/CHANGELOG.md index fb51ca3..a911495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,17 @@ +## v1.1.0 + +#### Changes + +* Support storyboard +* Support subspecs +* Upgrade to Swift 2.2 +* Use CocoaPods (1.0.1) + ## v1.0.0 Initial release written in Swift 2.1. -#### Added +#### Changes * `ICKeyboardDismissTextField` with an accessory view to dismiss keyboard. * `ICTokenField`, a text field that groups input texts as tokens. diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 591da6d..1716855 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -11,12 +11,15 @@ B52819581C90215C007D01D5 /* CustomizedTokenField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52819571C90215C007D01D5 /* CustomizedTokenField.swift */; }; B548C5A71C8D55A8009D5AEE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B548C5A51C8D55A8009D5AEE /* InfoPlist.strings */; }; B548C5A91C8D6150009D5AEE /* ExampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B548C5A81C8D6150009D5AEE /* ExampleCell.swift */; }; - B548C5C51C8E91B0009D5AEE /* ICInputAccessoryUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B548C5C41C8E91B0009D5AEE /* ICInputAccessoryUITests.swift */; }; + B548C5C51C8E91B0009D5AEE /* ICTokenFieldUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B548C5C41C8E91B0009D5AEE /* ICTokenFieldUITests.swift */; }; B5C53E191C92851400AF3489 /* CustomizedTokenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C53E181C92851400AF3489 /* CustomizedTokenViewController.swift */; }; + B5D04CFE1C99A7C900174823 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5D04CFD1C99A7C900174823 /* Main.storyboard */; }; + B5D04D001C99AF0700174823 /* StoryboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D04CFF1C99AF0700174823 /* StoryboardViewController.swift */; }; B5E9F8FF1C8D3B6E00443DC7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E9F8FE1C8D3B6E00443DC7 /* AppDelegate.swift */; }; B5E9F9011C8D3B6E00443DC7 /* ExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E9F9001C8D3B6E00443DC7 /* ExampleViewController.swift */; }; B5E9F9061C8D3B6E00443DC7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E9F9051C8D3B6E00443DC7 /* Assets.xcassets */; }; B5E9F9091C8D3B6E00443DC7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5E9F9071C8D3B6E00443DC7 /* LaunchScreen.storyboard */; }; + B5F62A5D1C9ECBCB003A1231 /* ICKeyboardDismissTextFieldUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F62A5C1C9ECBCB003A1231 /* ICKeyboardDismissTextFieldUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,15 +37,18 @@ B548C5A61C8D55A8009D5AEE /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; B548C5A81C8D6150009D5AEE /* ExampleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleCell.swift; sourceTree = ""; }; B548C5C21C8E91B0009D5AEE /* ICInputAccessoryUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ICInputAccessoryUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B548C5C41C8E91B0009D5AEE /* ICInputAccessoryUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICInputAccessoryUITests.swift; sourceTree = ""; }; + B548C5C41C8E91B0009D5AEE /* ICTokenFieldUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICTokenFieldUITests.swift; sourceTree = ""; }; B548C5C61C8E91B0009D5AEE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B5C53E181C92851400AF3489 /* CustomizedTokenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizedTokenViewController.swift; sourceTree = ""; }; + B5D04CFD1C99A7C900174823 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + B5D04CFF1C99AF0700174823 /* StoryboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardViewController.swift; sourceTree = ""; }; B5E9F8FB1C8D3B6E00443DC7 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; B5E9F8FE1C8D3B6E00443DC7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B5E9F9001C8D3B6E00443DC7 /* ExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleViewController.swift; sourceTree = ""; }; B5E9F9051C8D3B6E00443DC7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B5E9F9081C8D3B6E00443DC7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B5E9F90A1C8D3B6E00443DC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B5F62A5C1C9ECBCB003A1231 /* ICKeyboardDismissTextFieldUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ICKeyboardDismissTextFieldUITests.swift; sourceTree = ""; }; CA708B7D49E7D80A75ED81E3 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; D0DB1D3E89AB75183DB104E0 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FE1A89C8A77ED0438A206A24 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; @@ -87,7 +93,8 @@ B548C5C31C8E91B0009D5AEE /* ICInputAccessoryUITests */ = { isa = PBXGroup; children = ( - B548C5C41C8E91B0009D5AEE /* ICInputAccessoryUITests.swift */, + B5F62A5C1C9ECBCB003A1231 /* ICKeyboardDismissTextFieldUITests.swift */, + B548C5C41C8E91B0009D5AEE /* ICTokenFieldUITests.swift */, B548C5C61C8E91B0009D5AEE /* Info.plist */, ); path = ICInputAccessoryUITests; @@ -125,6 +132,8 @@ B5E9F90A1C8D3B6E00443DC7 /* Info.plist */, B548C5A51C8D55A8009D5AEE /* InfoPlist.strings */, B5E9F9071C8D3B6E00443DC7 /* LaunchScreen.storyboard */, + B5D04CFD1C99A7C900174823 /* Main.storyboard */, + B5D04CFF1C99AF0700174823 /* StoryboardViewController.swift */, ); path = Example; sourceTree = ""; @@ -154,12 +163,12 @@ isa = PBXNativeTarget; buildConfigurationList = B5E9F90D1C8D3B6E00443DC7 /* Build configuration list for PBXNativeTarget "Example" */; buildPhases = ( - 96705876C946719953BCD0C6 /* Check Pods Manifest.lock */, + 96705876C946719953BCD0C6 /* [CP] Check Pods Manifest.lock */, B5E9F8F71C8D3B6E00443DC7 /* Sources */, B5E9F8F81C8D3B6E00443DC7 /* Frameworks */, B5E9F8F91C8D3B6E00443DC7 /* Resources */, - CAB3B20531AAE2438C48D751 /* Embed Pods Frameworks */, - 7B2FB96C7361D5883F3ADD14 /* Copy Pods Resources */, + CAB3B20531AAE2438C48D751 /* [CP] Embed Pods Frameworks */, + 7B2FB96C7361D5883F3ADD14 /* [CP] Copy Pods Resources */, B5C50ADD1C917F4A0059032B /* Swift Lint */, ); buildRules = ( @@ -224,20 +233,21 @@ B5E9F9061C8D3B6E00443DC7 /* Assets.xcassets in Resources */, B548C5A71C8D55A8009D5AEE /* InfoPlist.strings in Resources */, B5E9F9091C8D3B6E00443DC7 /* LaunchScreen.storyboard in Resources */, + B5D04CFE1C99A7C900174823 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 7B2FB96C7361D5883F3ADD14 /* Copy Pods Resources */ = { + 7B2FB96C7361D5883F3ADD14 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -245,14 +255,14 @@ shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 96705876C946719953BCD0C6 /* Check Pods Manifest.lock */ = { + 96705876C946719953BCD0C6 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -274,14 +284,14 @@ shellPath = /bin/sh; shellScript = "if which swiftlint >/dev/null; then swiftlint; fi"; }; - CAB3B20531AAE2438C48D751 /* Embed Pods Frameworks */ = { + CAB3B20531AAE2438C48D751 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -296,7 +306,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B548C5C51C8E91B0009D5AEE /* ICInputAccessoryUITests.swift in Sources */, + B5F62A5D1C9ECBCB003A1231 /* ICKeyboardDismissTextFieldUITests.swift in Sources */, + B548C5C51C8E91B0009D5AEE /* ICTokenFieldUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -309,6 +320,7 @@ B5C53E191C92851400AF3489 /* CustomizedTokenViewController.swift in Sources */, B548C5A91C8D6150009D5AEE /* ExampleCell.swift in Sources */, B5E9F9011C8D3B6E00443DC7 /* ExampleViewController.swift in Sources */, + B5D04D001C99AF0700174823 /* StoryboardViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Example/CustomizedTokenViewController.swift b/Example/Example/CustomizedTokenViewController.swift index 5debfa7..2f7ae64 100644 --- a/Example/Example/CustomizedTokenViewController.swift +++ b/Example/Example/CustomizedTokenViewController.swift @@ -50,7 +50,7 @@ class CustomizedTokenViewController: UIViewController, ICTokenFieldDelegate { navigationController?.navigationBar.translucent = false navigationController?.navigationBar.barStyle = .Black - let cancelBarButton = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: Selector("dismiss:")) + let cancelBarButton = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: .dismiss) cancelBarButton.tintColor = UIColor.whiteColor() navigationItem.rightBarButtonItem = cancelBarButton @@ -72,15 +72,15 @@ class CustomizedTokenViewController: UIViewController, ICTokenFieldDelegate { // MARK: - ICTokenFieldDelegate func tokenFieldDidBeginEditing(tokenField: ICTokenField) { - print(__FUNCTION__) + print(#function) } func tokenFieldDidEndEditing(tokenField: ICTokenField) { - print(__FUNCTION__) + print(#function) } func tokenFieldWillReturn(tokenField: ICTokenField) { - print(__FUNCTION__) + print(#function) } func tokenField(tokenField: ICTokenField, didEnterText text: String) { @@ -95,7 +95,7 @@ class CustomizedTokenViewController: UIViewController, ICTokenFieldDelegate { // MARK: - UIResponder Callbacks - @IBAction private func dismiss(sender: UIBarButtonItem) { + @objc private func dismiss(sender: UIBarButtonItem) { presentingViewController?.dismissViewControllerAnimated(true, completion: nil) } @@ -106,3 +106,11 @@ class CustomizedTokenViewController: UIViewController, ICTokenFieldDelegate { } } + + +//////////////////////////////////////////////////////////////////////////////// + + +private extension Selector { + static let dismiss = #selector(CustomizedTokenViewController.dismiss(_:)) +} diff --git a/Example/Example/ExampleViewController.swift b/Example/Example/ExampleViewController.swift index c4a19a0..f9c6c4f 100644 --- a/Example/Example/ExampleViewController.swift +++ b/Example/Example/ExampleViewController.swift @@ -35,6 +35,14 @@ class ExampleViewController: UITableViewController { CustomizedTokenField.self ] + private lazy var flipButton: UIButton = { + let _button = UIButton(type: .System) + _button.frame = CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 88) + _button.setTitle("Storyboard", forState: .Normal) + _button.addTarget(self, action: .showStoryboard, forControlEvents: .TouchUpInside) + return _button + }() + // MARK: - Initialization convenience init() { @@ -47,6 +55,8 @@ class ExampleViewController: UITableViewController { override func loadView() { super.loadView() tableView.registerClass(ExampleCell.self, forCellReuseIdentifier: NSStringFromClass(ExampleCell.self)) + tableView.tableFooterView = flipButton + tableView.tableFooterView?.userInteractionEnabled } // MARK: - UITableViewDataSource @@ -113,4 +123,21 @@ class ExampleViewController: UITableViewController { } } + // MARK: - UIResponder Callbacks + + @objc private func showStoryboard(sender: UIButton) { + if let controller = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateInitialViewController() { + controller.modalTransitionStyle = .FlipHorizontal + presentViewController(controller, animated: true, completion: nil) + } + } + +} + + +//////////////////////////////////////////////////////////////////////////////// + + +private extension Selector { + static let showStoryboard = #selector(ExampleViewController.showStoryboard(_:)) } diff --git a/Example/Example/Info.plist b/Example/Example/Info.plist index 6d52a7c..f41df19 100644 --- a/Example/Example/Info.plist +++ b/Example/Example/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.0 + 1.1.0 CFBundleSignature ???? CFBundleVersion - 1 + 101 LSRequiresIPhoneOS UILaunchStoryboardName @@ -28,11 +28,14 @@ armv7 + UIRequiresFullScreen + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown diff --git a/Example/Example/Main.storyboard b/Example/Example/Main.storyboard new file mode 100644 index 0000000..3b44400 --- /dev/null +++ b/Example/Example/Main.storyboard @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/StoryboardViewController.swift b/Example/Example/StoryboardViewController.swift new file mode 100644 index 0000000..11936b7 --- /dev/null +++ b/Example/Example/StoryboardViewController.swift @@ -0,0 +1,50 @@ +// +// StoryboardViewController.swift +// Example +// +// Created by Ben on 16/03/2016. +// Copyright © 2016 Polydice, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import UIKit +import ICInputAccessory + +class StoryboardViewController: UITableViewController { + + @IBOutlet weak var tokenField: ICTokenField! { + didSet { + tokenField.normalTokenAttributes = [ + NSForegroundColorAttributeName: UIColor.whiteColor(), + NSBackgroundColorAttributeName: UIColor.whiteColor().colorWithAlphaComponent(0.25), + ] + + tokenField.highlightedTokenAttributes = [ + NSForegroundColorAttributeName: UIColor.darkGrayColor(), + NSBackgroundColorAttributeName: UIColor.whiteColor(), + ] + } + } + + @IBAction func dismiss(sender: UIButton) { + presentingViewController?.dismissViewControllerAnimated(true, completion: nil) + } + +} diff --git a/Example/ICInputAccessoryUITests/ICKeyboardDismissTextFieldUITests.swift b/Example/ICInputAccessoryUITests/ICKeyboardDismissTextFieldUITests.swift new file mode 100644 index 0000000..eee899c --- /dev/null +++ b/Example/ICInputAccessoryUITests/ICKeyboardDismissTextFieldUITests.swift @@ -0,0 +1,67 @@ +// +// ICKeyboardDismissTextFieldUITests.swift +// ICInputAccessoryUITests +// +// Created by Ben on 20/03/2016. +// Copyright © 2016 Polydice, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import XCTest + +class ICKeyboardDismissTextFieldUITests: XCTestCase { + + override func setUp() { + super.setUp() + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testKeyboardDismissing() { + let app = XCUIApplication() + app.tables.cells.textFields["ICKeyboardDismissTextField"].tap() + + let keyboardWindow = app.childrenMatchingType(.Window).elementBoundByIndex(1) + let accessory = keyboardWindow.childrenMatchingType(.Other).element.childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(0) + accessory.childrenMatchingType(.Button).element.tap() + } + + func testStoryboard() { + let app = XCUIApplication() + let tablesQuery = app.tables + + tablesQuery.buttons["Storyboard"].tap() + tablesQuery.textFields["Storyboard ICKeyboardDismissTextField"].tap() + + let keyboardWindow = app.childrenMatchingType(.Window).elementBoundByIndex(1) + let accessory = keyboardWindow.childrenMatchingType(.Other).element.childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(0) + accessory.childrenMatchingType(.Button).element.tap() + + tablesQuery.buttons["Back to Code"].tap() + } + +} \ No newline at end of file diff --git a/Example/ICInputAccessoryUITests/ICInputAccessoryUITests.swift b/Example/ICInputAccessoryUITests/ICTokenFieldUITests.swift similarity index 79% rename from Example/ICInputAccessoryUITests/ICInputAccessoryUITests.swift rename to Example/ICInputAccessoryUITests/ICTokenFieldUITests.swift index 0684bdd..4290f36 100644 --- a/Example/ICInputAccessoryUITests/ICInputAccessoryUITests.swift +++ b/Example/ICInputAccessoryUITests/ICTokenFieldUITests.swift @@ -1,5 +1,5 @@ // -// ICInputAccessoryUITests.swift +// ICTokenFieldUITests.swift // ICInputAccessoryUITests // // Created by Ben on 08/03/2016. @@ -26,7 +26,7 @@ import XCTest -class ICInputAccessoryUITests: XCTestCase { +class ICTokenFieldUITests: XCTestCase { override func setUp() { super.setUp() @@ -41,15 +41,6 @@ class ICInputAccessoryUITests: XCTestCase { super.tearDown() } - func testKeyboardDismissing() { - let app = XCUIApplication() - app.tables.cells.textFields["ICKeyboardDismissTextField"].tap() - - let keyboardWindow = app.childrenMatchingType(.Window).elementBoundByIndex(1) - let accessory = keyboardWindow.childrenMatchingType(.Other).element.childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(0) - accessory.childrenMatchingType(.Button).element.tap() - } - func testTokenField() { let app = XCUIApplication() let tablesQuery = app.tables @@ -97,4 +88,31 @@ class ICInputAccessoryUITests: XCTestCase { searchButton.tap() } + func testStoryboard() { + let app = XCUIApplication() + let tablesQuery = app.tables + tablesQuery.buttons["Storyboard"].tap() + + let tokenField = tablesQuery.cells.containingType(.StaticText, identifier:"Storyboard ICTokenField").childrenMatchingType(.TextField).element + tokenField.tap() + tokenField.typeText("Try") + tokenField.typeText(" ") + tokenField.typeText("iCook") + tokenField.typeText(",") + tokenField.typeText("beta") + tokenField.typeText(" ") + + let deleteKey = app.keys["delete"] + deleteKey.tap() + deleteKey.tap() + + tokenField.typeText("TestFlight") + tokenField.typeText(",") + + let searchButton = app.buttons["Search"] + searchButton.tap() + + tablesQuery.buttons["Back to Code"].tap() + } + } diff --git a/Example/ICInputAccessoryUITests/Info.plist b/Example/ICInputAccessoryUITests/Info.plist index ba72822..3b9aa4a 100644 --- a/Example/ICInputAccessoryUITests/Info.plist +++ b/Example/ICInputAccessoryUITests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0 + 1.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/Gemfile b/Gemfile index ec6fc6e..309c365 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "http://rubygems.org" +ruby "2.3.1" -gem "cocoapods", "~> 0.39.0" +gem "cocoapods", "~> 1.0.0" gem "pry" gem "rake" gem "xcpretty" diff --git a/Gemfile.lock b/Gemfile.lock index 38a3383..2ac56c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,65 +1,68 @@ GEM remote: http://rubygems.org/ specs: - activesupport (4.2.5.2) + activesupport (5.0.0) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - claide (0.9.1) - cocoapods (0.39.0) + claide (1.0.0) + cocoapods (1.0.1) activesupport (>= 4.0.2) - claide (~> 0.9.1) - cocoapods-core (= 0.39.0) - cocoapods-downloader (~> 0.9.3) - cocoapods-plugins (~> 0.4.2) - cocoapods-search (~> 0.1.0) - cocoapods-stats (~> 0.6.2) - cocoapods-trunk (~> 0.6.4) - cocoapods-try (~> 0.5.1) + claide (>= 1.0.0, < 2.0) + cocoapods-core (= 1.0.1) + cocoapods-deintegrate (>= 1.0.0, < 2.0) + cocoapods-downloader (>= 1.0.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.0.0, < 2.0) + cocoapods-try (>= 1.0.0, < 2.0) colored (~> 1.2) escape (~> 0.0.4) - molinillo (~> 0.4.0) + fourflusher (~> 0.3.0) + molinillo (~> 0.4.5) nap (~> 1.0) - xcodeproj (~> 0.28.2) - cocoapods-core (0.39.0) + xcodeproj (>= 1.1.0, < 2.0) + cocoapods-core (1.0.1) activesupport (>= 4.0.2) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-downloader (0.9.3) - cocoapods-plugins (0.4.2) + cocoapods-deintegrate (1.0.0) + cocoapods-downloader (1.1.0) + cocoapods-plugins (1.0.0) nap - cocoapods-search (0.1.0) - cocoapods-stats (0.6.2) - cocoapods-trunk (0.6.4) + cocoapods-search (1.0.0) + cocoapods-stats (1.0.0) + cocoapods-trunk (1.0.0) nap (>= 0.8, < 2.0) netrc (= 0.7.8) - cocoapods-try (0.5.1) + cocoapods-try (1.1.0) coderay (1.1.1) colored (1.2) + concurrent-ruby (1.0.2) escape (0.0.4) + fourflusher (0.3.2) fuzzy_match (2.0.4) i18n (0.7.0) - json (1.8.3) method_source (0.8.2) - minitest (5.8.4) - molinillo (0.4.4) + minitest (5.9.0) + molinillo (0.4.5) nap (1.1.0) netrc (0.7.8) - pry (0.10.3) + pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - rake (10.5.0) - rouge (1.10.1) + rake (11.2.2) + rouge (1.11.1) slop (3.6.0) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) - xcodeproj (0.28.2) + xcodeproj (1.2.0) activesupport (>= 3) - claide (~> 0.9.1) + claide (>= 1.0.0, < 2.0) colored (~> 1.2) xcpretty (0.2.2) rouge (~> 1.8) @@ -68,10 +71,13 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 0.39.0) + cocoapods (~> 1.0.0) pry rake xcpretty +RUBY VERSION + ruby 2.3.1p112 + BUNDLED WITH - 1.11.2 + 1.12.5 diff --git a/ICInputAccessory.podspec b/ICInputAccessory.podspec index 576c9b4..f2d77e7 100644 --- a/ICInputAccessory.podspec +++ b/ICInputAccessory.podspec @@ -1,26 +1,35 @@ Pod::Spec.new do |s| s.name = "ICInputAccessory" - s.version = "1.0.0" - s.summary = "Customized text fields used in the iCook app." + s.version = "1.1.0" + s.summary = "A customized token text field used in the iCook app." s.description = <<-DESC ICKeyboardDismissTextField: * An input accessory view with a button to dismiss keyboard. ICTokenField: * A horizontal scrolling UI that groups input texts. - * Easy to add and delete tokens. + * Easy to add, select and delete tokens. * Customizable icon and colors. + * Supports storyboard. DESC - s.screenshots = "https://raw.githubusercontent.com/polydice/ICInputAccessory/gh-pages/screenshots/ICKeyboardDismissTextField.png", - "https://raw.githubusercontent.com/polydice/ICInputAccessory/gh-pages/screenshots/ICTokenField.png" + s.screenshots = "https://polydice.github.io/ICInputAccessory/screenshots/ICTokenField.png", + "https://polydice.github.io/ICInputAccessory/screenshots/ICKeyboardDismissTextField.png" s.homepage = "https://github.com/polydice/ICInputAccessory" s.license = { type: "MIT", file: "LICENSE" } s.authors = "bcylin", "trisix" - s.platform = :ios, "8.0" s.source = { git: "https://github.com/polydice/ICInputAccessory.git", tag: "v#{s.version}" } - s.source_files = "Source/*.swift" - s.resources = "Source/*.xcassets" s.requires_arc = true + + s.default_subspecs = "KeyboardDismissTextField", "TokenField" + + s.subspec "KeyboardDismissTextField" do |sp| + sp.source_files = "Source/KeyboardDismissTextField/*.swift" + sp.resources = "Source/KeyboardDismissTextField/*.xcassets" + end + + s.subspec "TokenField" do |sp| + sp.source_files = "Source/TokenField/*.swift" + end end diff --git a/ICInputAccessory.xcodeproj/project.pbxproj b/ICInputAccessory.xcodeproj/project.pbxproj index 61e4b27..e71fbe0 100644 --- a/ICInputAccessory.xcodeproj/project.pbxproj +++ b/ICInputAccessory.xcodeproj/project.pbxproj @@ -18,16 +18,16 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - B52819681C9035BE007D01D5 /* ICBackspaceTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICBackspaceTextField.swift; path = Source/ICBackspaceTextField.swift; sourceTree = SOURCE_ROOT; }; - B52819691C9035BE007D01D5 /* ICInsetLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICInsetLabel.swift; path = Source/ICInsetLabel.swift; sourceTree = SOURCE_ROOT; }; - B528196A1C9035BE007D01D5 /* ICToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICToken.swift; path = Source/ICToken.swift; sourceTree = SOURCE_ROOT; }; - B528196B1C9035BE007D01D5 /* ICTokenField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICTokenField.swift; path = Source/ICTokenField.swift; sourceTree = SOURCE_ROOT; }; - B548C5AC1C8D69A5009D5AEE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Source/Images.xcassets; sourceTree = SOURCE_ROOT; }; - B548C5ED1C8EB9E2009D5AEE /* ICKeyboardDismissTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICKeyboardDismissTextField.swift; path = Source/ICKeyboardDismissTextField.swift; sourceTree = SOURCE_ROOT; }; + B52819681C9035BE007D01D5 /* ICBackspaceTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICBackspaceTextField.swift; path = Source/TokenField/ICBackspaceTextField.swift; sourceTree = SOURCE_ROOT; }; + B52819691C9035BE007D01D5 /* ICInsetLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICInsetLabel.swift; path = Source/TokenField/ICInsetLabel.swift; sourceTree = SOURCE_ROOT; }; + B528196A1C9035BE007D01D5 /* ICToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICToken.swift; path = Source/TokenField/ICToken.swift; sourceTree = SOURCE_ROOT; }; + B528196B1C9035BE007D01D5 /* ICTokenField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICTokenField.swift; path = Source/TokenField/ICTokenField.swift; sourceTree = SOURCE_ROOT; }; + B548C5AC1C8D69A5009D5AEE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Source/KeyboardDismissTextField/Images.xcassets; sourceTree = SOURCE_ROOT; }; + B548C5ED1C8EB9E2009D5AEE /* ICKeyboardDismissTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICKeyboardDismissTextField.swift; path = Source/KeyboardDismissTextField/ICKeyboardDismissTextField.swift; sourceTree = SOURCE_ROOT; }; B56BC42A1C89A7EA00C20AD6 /* ICInputAccessory.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ICInputAccessory.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B56BC42D1C89A7EA00C20AD6 /* ICInputAccessory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ICInputAccessory.h; sourceTree = ""; }; B56BC42F1C89A7EA00C20AD6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B56BC4351C89A8D800C20AD6 /* ICKeyboardDismissAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICKeyboardDismissAccessoryView.swift; path = Source/ICKeyboardDismissAccessoryView.swift; sourceTree = SOURCE_ROOT; }; + B56BC4351C89A8D800C20AD6 /* ICKeyboardDismissAccessoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ICKeyboardDismissAccessoryView.swift; path = Source/KeyboardDismissTextField/ICKeyboardDismissAccessoryView.swift; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ diff --git a/ICInputAccessory/Info.plist b/ICInputAccessory/Info.plist index 60b9c00..09bc972 100644 --- a/ICInputAccessory/Info.plist +++ b/ICInputAccessory/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.0 + 1.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/Podfile b/Podfile index 5e47f53..4d69ac8 100644 --- a/Podfile +++ b/Podfile @@ -2,9 +2,10 @@ platform :ios, "8.0" use_frameworks! workspace "ICInputAccessory" -xcodeproj "ICInputAccessory" -xcodeproj "Example/Example" +project "ICInputAccessory" +project "Example/Example" -target :Example do - pod "ICInputAccessory", path: "./" +target "Example" do + pod "ICInputAccessory/KeyboardDismissTextField", path: "./" + pod "ICInputAccessory/TokenField", path: "./" end diff --git a/Podfile.lock b/Podfile.lock index 3314940..31383d6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,14 +1,18 @@ PODS: - - ICInputAccessory (1.0.0) + - ICInputAccessory/KeyboardDismissTextField (1.1.0) + - ICInputAccessory/TokenField (1.1.0) DEPENDENCIES: - - ICInputAccessory (from `./`) + - ICInputAccessory/KeyboardDismissTextField (from `./`) + - ICInputAccessory/TokenField (from `./`) EXTERNAL SOURCES: ICInputAccessory: :path: "./" SPEC CHECKSUMS: - ICInputAccessory: 341225aaa67a035b266880a5795001b363d1b1da + ICInputAccessory: fa69a534ae8811866fa9d63a6b3ecf07bfced685 -COCOAPODS: 0.39.0 +PODFILE CHECKSUM: bc37f46eb6efd595acab704850534a8198e06d74 + +COCOAPODS: 1.0.1 diff --git a/README.md b/README.md index 2b70c4b..6546e0b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Try . [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/ICInputAccessory.svg)](https://img.shields.io/cocoapods/v/ICInputAccessory.svg) ![Platform](https://img.shields.io/cocoapods/p/ICInputAccessory.svg?style=flat) -![Swift 2.1.1](https://img.shields.io/badge/Swift-2.1.1-orange.svg) +![Swift 2.2](https://img.shields.io/badge/Swift-2.2-orange.svg) ### ICKeyboardDismissTextField @@ -18,12 +18,16 @@ Try . * A horizontal scrolling UI that groups input texts. * Easy to add, select and delete tokens. * Customizable icon and colors. +* Supports storyboard. -![ICTokenField](https://raw.githubusercontent.com/polydice/ICInputAccessory/gh-pages/screenshots/ICTokenField.gif) +![ICTokenField](https://polydice.github.io/ICInputAccessory/screenshots/ICTokenField.gif) ## Requirements -iOS 8.0+ with Xcode 7.2 or above. +ICInputAccessory | iOS | Xcode | Swift +---------------- | :--: | :---: | ----- +`~> v1.0.0` | 8.0+ | 7.2 | ![Swift 2.1.1](https://img.shields.io/badge/Swift-2.1.1-orange.svg) +`~> v1.1.0` | 8.0+ | 7.3 | ![Swift 2.2](https://img.shields.io/badge/Swift-2.2-orange.svg) ## Installation @@ -39,13 +43,14 @@ iOS 8.0+ with Xcode 7.2 or above. ### Install via [CocoaPods](http://guides.cocoapods.org/) -* Create a `Podfile` with the following specification and run `pod install`. +* **ICInputAccessory** supports subspecs. Create a `Podfile` with the following specification and run `pod install`. ```rb platform :ios, '8.0' use_frameworks! - pod 'ICInputAccessory' + pod 'ICInputAccessory/TokenField' + pod 'ICInputAccessory/KeyboardDismissTextField' ``` ### Install Manually @@ -106,6 +111,15 @@ public var normalTokenAttributes: [String : NSObject]? { get set } public var highlightedTokenAttributes: [String : NSObject]? { get set } ``` +* Customizable properties in storyboard: + +```swift +@IBInspectable var icon: UIImage? +@IBInspectable var placeholder: String? +@IBInspectable var textColor: UIColor? +@IBInspectable var cornerRadius: CGFloat +``` + See `Example/CustomizedTokenField.swift` for more details. #### ICTokenFieldDelegate @@ -120,15 +134,19 @@ See `Example/CustomizedTokenField.swift` for more details. ## Development -Meke sure you have [Homebrew](http://brew.sh/) installed, then run in the project root: +* Meke sure [Homebrew](http://brew.sh/) is installed. +* Current `develop` branch requires Ruby `2.3.1`. +* Set up dependencies by running the following command in the project root: -``` + ``` make setup ``` -Tasks for testing: +* Open **ICInputAccessory.xcworkspace** and run the demo app with the `Example` scheme. -``` +* See more tasks for building and testing: + + ``` rake -T ``` diff --git a/Rakefile b/Rakefile index 4885628..f138ad8 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,13 @@ task default: "ci:test" namespace :ci do + desc "Build targets on Travis CI with a specified OS version, default OS=latest" + task :build, [:os] do |t, args| + version = args[:os] || "latest" + Rake::Task["framework:build"].invoke version + Rake::Task["example:build"].invoke version + end + desc "Run tests on Travis CI with a specified OS version, default OS=latest" task :test, [:os] do |t, args| version = args[:os] || "latest" diff --git a/Source/ICKeyboardDismissAccessoryView.swift b/Source/KeyboardDismissTextField/ICKeyboardDismissAccessoryView.swift similarity index 84% rename from Source/ICKeyboardDismissAccessoryView.swift rename to Source/KeyboardDismissTextField/ICKeyboardDismissAccessoryView.swift index 51cdf79..8b1659d 100644 --- a/Source/ICKeyboardDismissAccessoryView.swift +++ b/Source/KeyboardDismissTextField/ICKeyboardDismissAccessoryView.swift @@ -26,8 +26,16 @@ import UIKit +@IBDesignable public class ICKeyboardDismissAccessoryView: UIView { + /// The background color of the button to dismiss keyboard. + @IBInspectable var buttonColor: UIColor = Constants.ButtonColor { + didSet { + dismissButton.backgroundColor = buttonColor + } + } + public private(set) lazy var dismissButton: UIButton = { let _button = UIButton() let resources = NSBundle(forClass: self.dynamicType) @@ -41,21 +49,27 @@ public class ICKeyboardDismissAccessoryView: UIView { return _button }() + private struct Constants { + static let ButtonColor = UIColor(red:0.21, green:0.2, blue:0.19, alpha:0.5) + static let EdgePadding = CGFloat(7) + static let InteractiveSize = CGSize(width: 44, height: 44) + } + // MARK: - Initialization - override public init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) setUpSubviews() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setUpSubviews() } // MARK: - UIView - override public func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { + public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { for subview in subviews { if !subview.hidden && subview.alpha > 0 && subview.userInteractionEnabled && @@ -66,14 +80,16 @@ public class ICKeyboardDismissAccessoryView: UIView { return false } - // MARK: - Private Methods + // MARK: - NSKeyValueCoding - private struct Constants { - static let ButtonColor = UIColor(red:0.21, green:0.2, blue:0.19, alpha:0.5) - static let EdgePadding = CGFloat(7) - static let InteractiveSize = CGSize(width: 44, height: 44) + public override func setValue(value: AnyObject?, forUndefinedKey key: String) { + if let color = value as? UIColor where key == "buttonColor" { + buttonColor = color + } } + // MARK: - Private Methods + private func setUpSubviews() { backgroundColor = UIColor.clearColor() @@ -101,7 +117,7 @@ public class ICKeyboardDismissAccessoryView: UIView { )) } - // MARK: - Public Methods + // MARK: - Internal Methods class func requiredHeight() -> CGFloat { return Constants.InteractiveSize.height + Constants.EdgePadding * 2 diff --git a/Source/ICKeyboardDismissTextField.swift b/Source/KeyboardDismissTextField/ICKeyboardDismissTextField.swift similarity index 65% rename from Source/ICKeyboardDismissTextField.swift rename to Source/KeyboardDismissTextField/ICKeyboardDismissTextField.swift index 2a2ac44..b0ecdf3 100644 --- a/Source/ICKeyboardDismissTextField.swift +++ b/Source/KeyboardDismissTextField/ICKeyboardDismissTextField.swift @@ -26,48 +26,59 @@ import UIKit +@IBDesignable public class ICKeyboardDismissTextField: UITextField { - private lazy var accessoryView: UIView = { - let _accessory = ICKeyboardDismissAccessoryView() - _accessory.dismissButton.addTarget(self, action: Selector("dismiss:"), forControlEvents: .TouchUpInside) - return _accessory - }() + @IBOutlet public var keyboardAccessoryView: ICKeyboardDismissAccessoryView! { + didSet { + if UI_USER_INTERFACE_IDIOM() != .Phone { return } + keyboardAccessoryView.dismissButton.addTarget(self, action: .dismiss, forControlEvents: .TouchUpInside) + inputAccessoryView = keyboardAccessoryView + } + } // MARK: - Initialization - override public init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) setUpAccessoryView() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setUpAccessoryView() } // MARK: - UIResponder - override public func becomeFirstResponder() -> Bool { + public override func becomeFirstResponder() -> Bool { if UI_USER_INTERFACE_IDIOM() == .Phone { - accessoryView.alpha = 1 + keyboardAccessoryView.alpha = 1 } return super.becomeFirstResponder() } + @objc private func dismiss(sender: UIButton) { + resignFirstResponder() + UIView.animateWithDuration(0.3) { + self.keyboardAccessoryView.alpha = 0 + } + } + // MARK: - Private Methods private func setUpAccessoryView() { - if UI_USER_INTERFACE_IDIOM() == .Phone { - inputAccessoryView = accessoryView + if keyboardAccessoryView == nil { + keyboardAccessoryView = ICKeyboardDismissAccessoryView() } } - @IBAction private func dismiss(sender: UIButton) { - resignFirstResponder() - UIView.animateWithDuration(0.3) { - self.accessoryView.alpha = 0 - } - } +} + + +//////////////////////////////////////////////////////////////////////////////// + +private extension Selector { + static let dismiss = #selector(ICKeyboardDismissTextField.dismiss(_:)) } diff --git a/Source/Images.xcassets/Contents.json b/Source/KeyboardDismissTextField/Images.xcassets/Contents.json similarity index 100% rename from Source/Images.xcassets/Contents.json rename to Source/KeyboardDismissTextField/Images.xcassets/Contents.json diff --git a/Source/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/Contents.json b/Source/KeyboardDismissTextField/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/Contents.json similarity index 100% rename from Source/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/Contents.json rename to Source/KeyboardDismissTextField/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/Contents.json diff --git a/Source/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/icook-iphone-button-hide-keyboard.pdf b/Source/KeyboardDismissTextField/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/icook-iphone-button-hide-keyboard.pdf similarity index 100% rename from Source/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/icook-iphone-button-hide-keyboard.pdf rename to Source/KeyboardDismissTextField/Images.xcassets/icook-iphone-button-hide-keyboard.imageset/icook-iphone-button-hide-keyboard.pdf diff --git a/Source/ICBackspaceTextField.swift b/Source/TokenField/ICBackspaceTextField.swift similarity index 100% rename from Source/ICBackspaceTextField.swift rename to Source/TokenField/ICBackspaceTextField.swift diff --git a/Source/ICInsetLabel.swift b/Source/TokenField/ICInsetLabel.swift similarity index 100% rename from Source/ICInsetLabel.swift rename to Source/TokenField/ICInsetLabel.swift diff --git a/Source/ICToken.swift b/Source/TokenField/ICToken.swift similarity index 100% rename from Source/ICToken.swift rename to Source/TokenField/ICToken.swift diff --git a/Source/ICTokenField.swift b/Source/TokenField/ICTokenField.swift similarity index 84% rename from Source/ICTokenField.swift rename to Source/TokenField/ICTokenField.swift index f2027cf..767bc0f 100644 --- a/Source/ICTokenField.swift +++ b/Source/TokenField/ICTokenField.swift @@ -44,8 +44,11 @@ import UIKit //////////////////////////////////////////////////////////////////////////////// +@IBDesignable public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDelegate { + // MARK: - Public Properties + /// The receiver’s delegate. public weak var delegate: ICTokenFieldDelegate? @@ -58,7 +61,7 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele } /// The image on the left of text field. - public var icon: UIImage? { + @IBInspectable public var icon: UIImage? { didSet { if let icon = icon { let imageView = UIImageView(image: icon) @@ -77,7 +80,7 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele } /// The placeholder with the default color and font. - public var placeholder: String? { + @IBInspectable public var placeholder: String? { get { return attributedPlaceholder?.string } @@ -128,6 +131,35 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele } } + /// The tint color of icon image and text field. + public override var tintColor: UIColor! { + didSet { + inputTextField.tintColor = tintColor + leftView?.tintColor = tintColor + } + } + + /// The text color of text field in the interface builder. Same as textField.text. + @IBInspectable var textColor: UIColor? { + get { + return inputTextField.textColor + } + set { + inputTextField.textColor = newValue + } + } + + /// The corner radius of token field in the interface builder. Same as layer.cornerRadius. + @IBInspectable var cornerRadius: CGFloat { + get { + return layer.cornerRadius + } + set { + layer.cornerRadius = newValue + layer.masksToBounds = newValue > 0 + } + } + // MARK: - Private Properties private var tokens = [ICToken]() @@ -140,21 +172,19 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele _textField.returnKeyType = .Search _textField.delegate = self _textField.backspaceDelegate = self - _textField.addTarget(self, action: Selector("togglePlaceholderIfNeeded:"), forControlEvents: .AllEditingEvents) + _textField.addTarget(self, action: .togglePlaceholderIfNeeded, forControlEvents: .AllEditingEvents) return _textField }() private var leftView: UIView? { didSet { oldValue?.removeFromSuperview() + leftEdgeConstraint.active = leftView == nil if let icon = leftView { addSubview(icon) icon.translatesAutoresizingMaskIntoConstraints = false addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[icon]-10-[wrapper]", options: [], metrics: nil, views: ["icon": icon, "wrapper": scrollView])) addConstraint(NSLayoutConstraint(item: icon, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1, constant: 0)) - leftEdgeConstraint.active = false - } else { - leftEdgeConstraint.active = true } } } @@ -176,43 +206,60 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele }() private lazy var tapGestureRecognizer: UITapGestureRecognizer = { - UITapGestureRecognizer(target: self, action: Selector("handleTapGesture:")) + UITapGestureRecognizer(target: self, action: .handleTapGesture) }() // MARK: - Initialization - override public init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) setUpSubviews() } - required public init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setUpSubviews() } // MARK: - UIResponder - override public func isFirstResponder() -> Bool { + public override func isFirstResponder() -> Bool { return inputTextField.isFirstResponder() || super.isFirstResponder() } - override public func becomeFirstResponder() -> Bool { + public override func becomeFirstResponder() -> Bool { return inputTextField.becomeFirstResponder() } - override public func resignFirstResponder() -> Bool { + public override func resignFirstResponder() -> Bool { super.resignFirstResponder() return inputTextField.resignFirstResponder() } // MARK: - UIView - override public func layoutSubviews() { + public override func layoutSubviews() { super.layoutSubviews() layoutTokenTextField() } + // MARK: - NSKeyValueCoding + + public override func setValue(value: AnyObject?, forUndefinedKey key: String) { + switch value { + case let image as UIImage? where key == "icon": + icon = image + case let text as String? where key == "placeholder": + placeholder = text + case let color as UIColor? where key == "textColor": + textColor = color + case let value as CGFloat where key == "cornerRadius": + cornerRadius = value + default: + break + } + } + // MARK: - UITextFieldDelegate public func textFieldShouldBeginEditing(textField: UITextField) -> Bool { @@ -235,9 +282,9 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele removeHighlightedToken() // as user starts typing when a token is focused inputTextField.showsCursor = true - guard - let input = textField.text, - let text: NSString = (input as NSString).stringByReplacingCharactersInRange(range, withString: string) + guard let + input = textField.text, + text: NSString = (input as NSString).stringByReplacingCharactersInRange(range, withString: string) else { return true } @@ -289,12 +336,12 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele // MARK: - UIResponder Callbacks - @IBAction private func togglePlaceholderIfNeeded(sender: UITextField? = nil) { + @objc private func togglePlaceholderIfNeeded(sender: UITextField? = nil) { let showsPlaceholder = tokens.isEmpty && (inputTextField.text?.isEmpty ?? true) placeholderLabel.hidden = !showsPlaceholder } - @IBAction private func handleTapGesture(sender: UITapGestureRecognizer) { + @objc private func handleTapGesture(sender: UITapGestureRecognizer) { if !isFirstResponder() { inputTextField.becomeFirstResponder() } @@ -340,8 +387,6 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele frame = CGRect(x: 0, y: 7, width: UIScreen.mainScreen().bounds.width, height: 30) } - backgroundColor = UIColor.whiteColor() - addSubview(scrollView) scrollView.addSubview(inputTextField) scrollView.translatesAutoresizingMaskIntoConstraints = false @@ -409,3 +454,12 @@ public class ICTokenField: UIView, UITextFieldDelegate, ICBackspaceTextFieldDele } } + + +//////////////////////////////////////////////////////////////////////////////// + + +private extension Selector { + static let togglePlaceholderIfNeeded = #selector(ICTokenField.togglePlaceholderIfNeeded(_:)) + static let handleTapGesture = #selector(ICTokenField.handleTapGesture(_:)) +}