diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..e0bf90a
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,23 @@
+name: Build
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Build
+ run: xcodebuild -project SearchTextfield.xcodeproj -target SearchTextField -destination 'platform=iOS Simulator,name=iPhone 11,OS=13.5'
+ # - name: Run tests
+ # run: swift test -v
+ swiftlint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: SwiftLint
+ uses: norio-nomura/action-swiftlint@3.1.0
diff --git a/.gitignore b/.gitignore
index 9bce6af..1dcb724 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
xcuserdata
+.build/
+**/build/*
\ No newline at end of file
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 0000000..a6a1ff7
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,7 @@
+# format options
+--indent tab
+--tabwidth 4
+
+# file options
+--exclude .Build
+--exclude Example
\ No newline at end of file
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 0000000..a329f26
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,6 @@
+disabled_rules:
+ - trailing_comma
+excluded:
+ - .build
+ - Example
+ - Tests
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..547e427
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,29 @@
+// swift-tools-version:5.2
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "SearchTextField",
+ platforms: [.iOS(.v9)],
+ products: [
+ // Products define the executables and libraries produced by a package, and make them visible to other packages.
+ .library(
+ name: "SearchTextField",
+ targets: ["SearchTextField"]
+ ),
+ ],
+ dependencies: [],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages which this package depends on.
+ .target(
+ name: "SearchTextField",
+ dependencies: []
+ ),
+ .testTarget(
+ name: "SearchTextFieldTests",
+ dependencies: ["SearchTextField"]
+ ),
+ ]
+)
diff --git a/README.md b/README.md
index 409e9d9..0989802 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
# SearchTextField
+
[](http://cocoapods.org/pods/SearchTextField)
[](http://cocoapods.org/pods/SearchTextField)
[](http://cocoapods.org/pods/SearchTextField)
@@ -24,7 +25,14 @@ Now you can make suggestions "inline", showing the first matched result as the p
## Installation
-SearchTextField is available through [CocoaPods](http://cocoapods.org). To install
+SearchTextField is available through [SwiftPM](https://swift.org/package-manager/)
+
+To install via SPM:
+1. xcode -> file -> Swift Packages -> Add Package Dependency
+2. search for SearchTextField or use the git clone url.
+3. select the branch, version, or commit you want to use.
+
+SearchTextField is also available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```swift
diff --git a/SearchTextField.podspec b/SearchTextField.podspec
index 0c01f23..ef30567 100644
--- a/SearchTextField.podspec
+++ b/SearchTextField.podspec
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = "SearchTextField"
- s.version = "1.2.4"
+ s.version = "1.2.5"
s.summary = "SearchTextField extends UITextField allowing you to add the autocomplete feature in a really easy way"
s.swift_version = "5.0"
@@ -37,7 +37,7 @@ SearchTextField supports two different modes: the classic dropdown list (by defa
s.user_target_xcconfig = { 'ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES' => '$(inherited)' }
- s.source_files = 'SearchTextField/Classes/**/*'
+ s.source_files = 'Sources/SearchTextField/Classes/**/*'
#s.resource_bundles = {
# 'SearchTextField' => ['SearchTextField/Assets/*.png']
#}
diff --git a/SearchTextField.xcodeproj/SearchTextFieldTests_Info.plist b/SearchTextField.xcodeproj/SearchTextFieldTests_Info.plist
new file mode 100644
index 0000000..7c23420
--- /dev/null
+++ b/SearchTextField.xcodeproj/SearchTextFieldTests_Info.plist
@@ -0,0 +1,25 @@
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/SearchTextField.xcodeproj/SearchTextField_Info.plist b/SearchTextField.xcodeproj/SearchTextField_Info.plist
new file mode 100644
index 0000000..57ada9f
--- /dev/null
+++ b/SearchTextField.xcodeproj/SearchTextField_Info.plist
@@ -0,0 +1,25 @@
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/SearchTextField.xcodeproj/project.pbxproj b/SearchTextField.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..34172c0
--- /dev/null
+++ b/SearchTextField.xcodeproj/project.pbxproj
@@ -0,0 +1,508 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ "SearchTextField::SearchTextFieldPackageTests::ProductTarget" /* SearchTextFieldPackageTests */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = OBJ_35 /* Build configuration list for PBXAggregateTarget "SearchTextFieldPackageTests" */;
+ buildPhases = (
+ );
+ dependencies = (
+ OBJ_38 /* PBXTargetDependency */,
+ );
+ name = SearchTextFieldPackageTests;
+ productName = SearchTextFieldPackageTests;
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ OBJ_26 /* SearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* SearchTextField.swift */; };
+ OBJ_33 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
+ OBJ_44 /* SearchTextFieldTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* SearchTextFieldTest.swift */; };
+ OBJ_46 /* SearchTextField.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "SearchTextField::SearchTextField::Product" /* SearchTextField.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 84DCF4CD24773E2400CF9185 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = OBJ_1 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = "SearchTextField::SearchTextField";
+ remoteInfo = SearchTextField;
+ };
+ 84DCF4CE24773E2C00CF9185 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = OBJ_1 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = "SearchTextField::SearchTextFieldTests";
+ remoteInfo = SearchTextFieldTests;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ OBJ_10 /* SearchTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTextField.swift; sourceTree = ""; };
+ OBJ_13 /* SearchTextFieldTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTextFieldTest.swift; sourceTree = ""; };
+ OBJ_17 /* Example */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Example; sourceTree = SOURCE_ROOT; };
+ OBJ_18 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
+ OBJ_19 /* SearchTextField.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SearchTextField.podspec; sourceTree = ""; };
+ OBJ_20 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
+ OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
+ "SearchTextField::SearchTextField::Product" /* SearchTextField.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SearchTextField.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ "SearchTextField::SearchTextFieldTests::Product" /* SearchTextFieldTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = SearchTextFieldTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ OBJ_27 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 0;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ OBJ_45 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 0;
+ files = (
+ OBJ_46 /* SearchTextField.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ OBJ_11 /* Tests */ = {
+ isa = PBXGroup;
+ children = (
+ OBJ_12 /* SearchTextFieldTests */,
+ );
+ name = Tests;
+ sourceTree = SOURCE_ROOT;
+ };
+ OBJ_12 /* SearchTextFieldTests */ = {
+ isa = PBXGroup;
+ children = (
+ OBJ_13 /* SearchTextFieldTest.swift */,
+ );
+ name = SearchTextFieldTests;
+ path = Tests/SearchTextFieldTests;
+ sourceTree = SOURCE_ROOT;
+ };
+ OBJ_14 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ "SearchTextField::SearchTextFieldTests::Product" /* SearchTextFieldTests.xctest */,
+ "SearchTextField::SearchTextField::Product" /* SearchTextField.framework */,
+ );
+ name = Products;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ OBJ_5 /* */ = {
+ isa = PBXGroup;
+ children = (
+ OBJ_6 /* Package.swift */,
+ OBJ_7 /* Sources */,
+ OBJ_11 /* Tests */,
+ OBJ_14 /* Products */,
+ OBJ_17 /* Example */,
+ OBJ_18 /* LICENSE */,
+ OBJ_19 /* SearchTextField.podspec */,
+ OBJ_20 /* README.md */,
+ );
+ name = "";
+ sourceTree = "";
+ };
+ OBJ_7 /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ OBJ_8 /* SearchTextField */,
+ );
+ name = Sources;
+ sourceTree = SOURCE_ROOT;
+ };
+ OBJ_8 /* SearchTextField */ = {
+ isa = PBXGroup;
+ children = (
+ OBJ_9 /* Classes */,
+ );
+ name = SearchTextField;
+ path = Sources/SearchTextField;
+ sourceTree = SOURCE_ROOT;
+ };
+ OBJ_9 /* Classes */ = {
+ isa = PBXGroup;
+ children = (
+ OBJ_10 /* SearchTextField.swift */,
+ );
+ path = Classes;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ "SearchTextField::SearchTextField" /* SearchTextField */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = OBJ_22 /* Build configuration list for PBXNativeTarget "SearchTextField" */;
+ buildPhases = (
+ OBJ_25 /* Sources */,
+ OBJ_27 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SearchTextField;
+ productName = SearchTextField;
+ productReference = "SearchTextField::SearchTextField::Product" /* SearchTextField.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ "SearchTextField::SearchTextFieldTests" /* SearchTextFieldTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = OBJ_40 /* Build configuration list for PBXNativeTarget "SearchTextFieldTests" */;
+ buildPhases = (
+ OBJ_43 /* Sources */,
+ OBJ_45 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ OBJ_47 /* PBXTargetDependency */,
+ );
+ name = SearchTextFieldTests;
+ productName = SearchTextFieldTests;
+ productReference = "SearchTextField::SearchTextFieldTests::Product" /* SearchTextFieldTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ "SearchTextField::SwiftPMPackageDescription" /* SearchTextFieldPackageDescription */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = OBJ_29 /* Build configuration list for PBXNativeTarget "SearchTextFieldPackageDescription" */;
+ buildPhases = (
+ OBJ_32 /* Sources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SearchTextFieldPackageDescription;
+ productName = SearchTextFieldPackageDescription;
+ productType = "com.apple.product-type.framework";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ OBJ_1 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftMigration = 9999;
+ LastUpgradeCheck = 9999;
+ };
+ buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "SearchTextField" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = OBJ_5 /* */;
+ productRefGroup = OBJ_14 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ "SearchTextField::SearchTextField" /* SearchTextField */,
+ "SearchTextField::SwiftPMPackageDescription" /* SearchTextFieldPackageDescription */,
+ "SearchTextField::SearchTextFieldPackageTests::ProductTarget" /* SearchTextFieldPackageTests */,
+ "SearchTextField::SearchTextFieldTests" /* SearchTextFieldTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ OBJ_25 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 0;
+ files = (
+ OBJ_26 /* SearchTextField.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ OBJ_32 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 0;
+ files = (
+ OBJ_33 /* Package.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ OBJ_43 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 0;
+ files = (
+ OBJ_44 /* SearchTextFieldTest.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ OBJ_38 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = "SearchTextField::SearchTextFieldTests" /* SearchTextFieldTests */;
+ targetProxy = 84DCF4CE24773E2C00CF9185 /* PBXContainerItemProxy */;
+ };
+ OBJ_47 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = "SearchTextField::SearchTextField" /* SearchTextField */;
+ targetProxy = 84DCF4CD24773E2400CF9185 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ OBJ_23 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ENABLE_TESTABILITY = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PLATFORM_DIR)/Developer/Library/Frameworks",
+ );
+ HEADER_SEARCH_PATHS = "$(inherited)";
+ INFOPLIST_FILE = SearchTextField.xcodeproj/SearchTextField_Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ OTHER_CFLAGS = "$(inherited)";
+ OTHER_LDFLAGS = "$(inherited)";
+ OTHER_SWIFT_FLAGS = "$(inherited)";
+ PRODUCT_BUNDLE_IDENTIFIER = SearchTextField;
+ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
+ SWIFT_VERSION = 5.0;
+ TARGET_NAME = SearchTextField;
+ TVOS_DEPLOYMENT_TARGET = 9.0;
+ WATCHOS_DEPLOYMENT_TARGET = 2.0;
+ };
+ name = Debug;
+ };
+ OBJ_24 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ENABLE_TESTABILITY = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PLATFORM_DIR)/Developer/Library/Frameworks",
+ );
+ HEADER_SEARCH_PATHS = "$(inherited)";
+ INFOPLIST_FILE = SearchTextField.xcodeproj/SearchTextField_Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ ONLY_ACTIVE_ARCH = YES;
+ OTHER_CFLAGS = "$(inherited)";
+ OTHER_LDFLAGS = "$(inherited)";
+ OTHER_SWIFT_FLAGS = "$(inherited)";
+ PRODUCT_BUNDLE_IDENTIFIER = SearchTextField;
+ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
+ SWIFT_VERSION = 5.0;
+ TARGET_NAME = SearchTextField;
+ TVOS_DEPLOYMENT_TARGET = 9.0;
+ WATCHOS_DEPLOYMENT_TARGET = 2.0;
+ };
+ name = Release;
+ };
+ OBJ_3 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_OBJC_ARC = YES;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_NS_ASSERTIONS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "SWIFT_PACKAGE=1",
+ "DEBUG=1",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ ONLY_ACTIVE_ARCH = NO;
+ OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ USE_HEADERMAP = NO;
+ };
+ name = Debug;
+ };
+ OBJ_30 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ LD = /usr/bin/true;
+ OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.2.0";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ OBJ_31 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ LD = /usr/bin/true;
+ OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.2.0";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ OBJ_36 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ };
+ name = Debug;
+ };
+ OBJ_37 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ };
+ name = Release;
+ };
+ OBJ_4 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_OBJC_ARC = YES;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ GCC_OPTIMIZATION_LEVEL = s;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "SWIFT_PACKAGE=1",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ ONLY_ACTIVE_ARCH = NO;
+ OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE";
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ USE_HEADERMAP = NO;
+ };
+ name = Release;
+ };
+ OBJ_41 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PLATFORM_DIR)/Developer/Library/Frameworks",
+ );
+ HEADER_SEARCH_PATHS = "$(inherited)";
+ INFOPLIST_FILE = SearchTextField.xcodeproj/SearchTextFieldTests_Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ OTHER_CFLAGS = "$(inherited)";
+ OTHER_LDFLAGS = "$(inherited)";
+ OTHER_SWIFT_FLAGS = "$(inherited)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
+ SWIFT_VERSION = 5.0;
+ TARGET_NAME = SearchTextFieldTests;
+ TVOS_DEPLOYMENT_TARGET = 9.0;
+ WATCHOS_DEPLOYMENT_TARGET = 2.0;
+ };
+ name = Debug;
+ };
+ OBJ_42 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PLATFORM_DIR)/Developer/Library/Frameworks",
+ );
+ HEADER_SEARCH_PATHS = "$(inherited)";
+ INFOPLIST_FILE = SearchTextField.xcodeproj/SearchTextFieldTests_Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ OTHER_CFLAGS = "$(inherited)";
+ OTHER_LDFLAGS = "$(inherited)";
+ OTHER_SWIFT_FLAGS = "$(inherited)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
+ SWIFT_VERSION = 5.0;
+ TARGET_NAME = SearchTextFieldTests;
+ TVOS_DEPLOYMENT_TARGET = 9.0;
+ WATCHOS_DEPLOYMENT_TARGET = 2.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ OBJ_2 /* Build configuration list for PBXProject "SearchTextField" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ OBJ_3 /* Debug */,
+ OBJ_4 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ OBJ_22 /* Build configuration list for PBXNativeTarget "SearchTextField" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ OBJ_23 /* Debug */,
+ OBJ_24 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ OBJ_29 /* Build configuration list for PBXNativeTarget "SearchTextFieldPackageDescription" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ OBJ_30 /* Debug */,
+ OBJ_31 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ OBJ_35 /* Build configuration list for PBXAggregateTarget "SearchTextFieldPackageTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ OBJ_36 /* Debug */,
+ OBJ_37 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ OBJ_40 /* Build configuration list for PBXNativeTarget "SearchTextFieldTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ OBJ_41 /* Debug */,
+ OBJ_42 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = OBJ_1 /* Project object */;
+}
diff --git a/SearchTextField.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SearchTextField.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..fe1aa71
--- /dev/null
+++ b/SearchTextField.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/SearchTextField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SearchTextField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/SearchTextField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/SearchTextField.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SearchTextField.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..a72dc2b
--- /dev/null
+++ b/SearchTextField.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
+
+
+
\ No newline at end of file
diff --git a/SearchTextField.xcodeproj/xcshareddata/xcschemes/SearchTextField-Package.xcscheme b/SearchTextField.xcodeproj/xcshareddata/xcschemes/SearchTextField-Package.xcscheme
new file mode 100644
index 0000000..5f0371c
--- /dev/null
+++ b/SearchTextField.xcodeproj/xcshareddata/xcschemes/SearchTextField-Package.xcscheme
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SearchTextField/Classes/.gitkeep b/SearchTextField/Classes/.gitkeep
deleted file mode 100755
index e69de29..0000000
diff --git a/SearchTextField/Classes/SearchTextField.swift b/SearchTextField/Classes/SearchTextField.swift
deleted file mode 100755
index 9cf8f55..0000000
--- a/SearchTextField/Classes/SearchTextField.swift
+++ /dev/null
@@ -1,680 +0,0 @@
-//
-// SearchTextField.swift
-// SearchTextField
-//
-// Created by Alejandro Pasccon on 4/20/16.
-// Copyright © 2016 Alejandro Pasccon. All rights reserved.
-//
-
-import UIKit
-
-open class SearchTextField: UITextField {
-
- ////////////////////////////////////////////////////////////////////////
- // Public interface
-
- /// Maximum number of results to be shown in the suggestions list
- open var maxNumberOfResults = 0
-
- /// Maximum height of the results list
- open var maxResultsListHeight = 0
-
- /// Indicate if this field has been interacted with yet
- open var interactedWith = false
-
- /// Indicate if keyboard is showing or not
- open var keyboardIsShowing = false
-
- /// How long to wait before deciding typing has stopped
- open var typingStoppedDelay = 0.8
-
- /// Set your custom visual theme, or just choose between pre-defined SearchTextFieldTheme.lightTheme() and SearchTextFieldTheme.darkTheme() themes
- open var theme = SearchTextFieldTheme.lightTheme() {
- didSet {
- tableView?.reloadData()
-
- if let placeholderColor = theme.placeholderColor {
- if let placeholderString = placeholder {
- self.attributedPlaceholder = NSAttributedString(string: placeholderString, attributes: [NSAttributedString.Key.foregroundColor: placeholderColor])
- }
-
- self.placeholderLabel?.textColor = placeholderColor
- }
-
- if let hightlightedFont = self.highlightAttributes[.font] as? UIFont {
- self.highlightAttributes[.font] = hightlightedFont.withSize(self.theme.font.pointSize)
- }
- }
- }
-
- /// Show the suggestions list without filter when the text field is focused
- open var startVisible = false
-
- /// Show the suggestions list without filter even if the text field is not focused
- open var startVisibleWithoutInteraction = false {
- didSet {
- if startVisibleWithoutInteraction {
- textFieldDidChange()
- }
- }
- }
-
- /// Set an array of SearchTextFieldItem's to be used for suggestions
- open func filterItems(_ items: [SearchTextFieldItem]) {
- filterDataSource = items
- }
-
- /// Set an array of strings to be used for suggestions
- open func filterStrings(_ strings: [String]) {
- var items = [SearchTextFieldItem]()
-
- for value in strings {
- items.append(SearchTextFieldItem(title: value))
- }
-
- filterItems(items)
- }
-
- /// Closure to handle when the user pick an item
- open var itemSelectionHandler: SearchTextFieldItemHandler?
-
- /// Closure to handle when the user stops typing
- open var userStoppedTypingHandler: (() -> Void)?
-
- /// Set your custom set of attributes in order to highlight the string found in each item
- open var highlightAttributes: [NSAttributedString.Key: AnyObject] = [.font: UIFont.boldSystemFont(ofSize: 10)]
-
- /// Start showing the default loading indicator, useful for searches that take some time.
- open func showLoadingIndicator() {
- self.rightViewMode = .always
- indicator.startAnimating()
- }
-
- /// Force the results list to adapt to RTL languages
- open var forceRightToLeft = false
-
- /// Hide the default loading indicator
- open func stopLoadingIndicator() {
- self.rightViewMode = .never
- indicator.stopAnimating()
- }
-
- /// When InlineMode is true, the suggestions appear in the same line than the entered string. It's useful for email domains suggestion for example.
- open var inlineMode: Bool = false {
- didSet {
- if inlineMode == true {
- autocorrectionType = .no
- spellCheckingType = .no
- }
- }
- }
-
- /// Only valid when InlineMode is true. The suggestions appear after typing the provided string (or even better a character like '@')
- open var startFilteringAfter: String?
-
- /// Min number of characters to start filtering
- open var minCharactersNumberToStartFiltering: Int = 0
-
- /// Force no filtering (display the entire filtered data source)
- open var forceNoFiltering: Bool = false
-
- /// If startFilteringAfter is set, and startSuggestingImmediately is true, the list of suggestions appear immediately
- open var startSuggestingImmediately = false
-
- /// Allow to decide the comparision options
- open var comparisonOptions: NSString.CompareOptions = [.caseInsensitive]
-
- /// Set the results list's header
- open var resultsListHeader: UIView?
-
- // Move the table around to customize for your layout
- open var tableXOffset: CGFloat = 0.0
- open var tableYOffset: CGFloat = 0.0
- open var tableCornerRadius: CGFloat = 2.0
- open var tableBottomMargin: CGFloat = 10.0
-
- ////////////////////////////////////////////////////////////////////////
- // Private implementation
-
- fileprivate var tableView: UITableView?
- fileprivate var shadowView: UIView?
- fileprivate var direction: Direction = .down
- fileprivate var fontConversionRate: CGFloat = 0.7
- fileprivate var keyboardFrame: CGRect?
- fileprivate var timer: Timer? = nil
- fileprivate var placeholderLabel: UILabel?
- fileprivate static let cellIdentifier = "APSearchTextFieldCell"
- fileprivate let indicator = UIActivityIndicatorView(style: .gray)
- fileprivate var maxTableViewSize: CGFloat = 0
-
- fileprivate var filteredResults = [SearchTextFieldItem]()
- fileprivate var filterDataSource = [SearchTextFieldItem]() {
- didSet {
- filter(forceShowAll: forceNoFiltering)
- buildSearchTableView()
-
- if startVisibleWithoutInteraction {
- textFieldDidChange()
- }
- }
- }
-
- fileprivate var currentInlineItem = ""
-
- deinit {
- NotificationCenter.default.removeObserver(self)
- }
-
- open override func willMove(toWindow newWindow: UIWindow?) {
- super.willMove(toWindow: newWindow)
- tableView?.removeFromSuperview()
- }
-
- override open func willMove(toSuperview newSuperview: UIView?) {
- super.willMove(toSuperview: newSuperview)
-
- self.addTarget(self, action: #selector(SearchTextField.textFieldDidChange), for: .editingChanged)
- self.addTarget(self, action: #selector(SearchTextField.textFieldDidBeginEditing), for: .editingDidBegin)
- self.addTarget(self, action: #selector(SearchTextField.textFieldDidEndEditing), for: .editingDidEnd)
- self.addTarget(self, action: #selector(SearchTextField.textFieldDidEndEditingOnExit), for: .editingDidEndOnExit)
-
- NotificationCenter.default.addObserver(self, selector: #selector(SearchTextField.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(SearchTextField.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(SearchTextField.keyboardDidChangeFrame(_:)), name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
- }
-
- override open func layoutSubviews() {
- super.layoutSubviews()
-
- if inlineMode {
- buildPlaceholderLabel()
- } else {
- buildSearchTableView()
- }
-
- // Create the loading indicator
- indicator.hidesWhenStopped = true
- self.rightView = indicator
- }
-
- override open func rightViewRect(forBounds bounds: CGRect) -> CGRect {
- var rightFrame = super.rightViewRect(forBounds: bounds)
- rightFrame.origin.x -= 5
- return rightFrame
- }
-
- // Create the filter table and shadow view
- fileprivate func buildSearchTableView() {
- guard let tableView = tableView, let shadowView = shadowView else {
- self.tableView = UITableView(frame: CGRect.zero)
- self.shadowView = UIView(frame: CGRect.zero)
- buildSearchTableView()
- return
- }
-
- tableView.layer.masksToBounds = true
- tableView.layer.borderWidth = theme.borderWidth > 0 ? theme.borderWidth : 0.5
- tableView.dataSource = self
- tableView.delegate = self
- tableView.separatorInset = UIEdgeInsets.zero
- tableView.tableHeaderView = resultsListHeader
- if forceRightToLeft {
- tableView.semanticContentAttribute = .forceRightToLeft
- }
-
- shadowView.backgroundColor = UIColor.lightText
- shadowView.layer.shadowColor = UIColor.black.cgColor
- shadowView.layer.shadowOffset = CGSize.zero
- shadowView.layer.shadowOpacity = 1
-
- self.window?.addSubview(tableView)
-
- redrawSearchTableView()
- }
-
- fileprivate func buildPlaceholderLabel() {
- var newRect = self.placeholderRect(forBounds: self.bounds)
- var caretRect = self.caretRect(for: self.beginningOfDocument)
- let textRect = self.textRect(forBounds: self.bounds)
-
- if let range = textRange(from: beginningOfDocument, to: endOfDocument) {
- caretRect = self.firstRect(for: range)
- }
-
- newRect.origin.x = caretRect.origin.x + caretRect.size.width + textRect.origin.x
- newRect.size.width = newRect.size.width - newRect.origin.x
-
- if let placeholderLabel = placeholderLabel {
- placeholderLabel.font = self.font
- placeholderLabel.frame = newRect
- } else {
- placeholderLabel = UILabel(frame: newRect)
- placeholderLabel?.font = self.font
- placeholderLabel?.backgroundColor = UIColor.clear
- placeholderLabel?.lineBreakMode = .byClipping
-
- if let placeholderColor = self.attributedPlaceholder?.attribute(NSAttributedString.Key.foregroundColor, at: 0, effectiveRange: nil) as? UIColor {
- placeholderLabel?.textColor = placeholderColor
- } else {
- placeholderLabel?.textColor = UIColor ( red: 0.8, green: 0.8, blue: 0.8, alpha: 1.0 )
- }
-
- self.addSubview(placeholderLabel!)
- }
- }
-
- // Re-set frames and theme colors
- fileprivate func redrawSearchTableView() {
- if inlineMode {
- tableView?.isHidden = true
- return
- }
-
- if let tableView = tableView {
- guard let frame = self.superview?.convert(self.frame, to: nil) else { return }
-
- //TableViews use estimated cell heights to calculate content size until they
- // are on-screen. We must set this to the theme cell height to avoid getting an
- // incorrect contentSize when we have specified non-standard fonts and/or
- // cellHeights in the theme. We do it here to ensure updates to these settings
- // are recognized if changed after the tableView is created
- tableView.estimatedRowHeight = theme.cellHeight
- if self.direction == .down {
-
- var tableHeight: CGFloat = 0
- if keyboardIsShowing, let keyboardHeight = keyboardFrame?.size.height {
- tableHeight = min((tableView.contentSize.height), (UIScreen.main.bounds.size.height - frame.origin.y - frame.height - keyboardHeight))
- } else {
- tableHeight = min((tableView.contentSize.height), (UIScreen.main.bounds.size.height - frame.origin.y - frame.height))
- }
-
- if maxResultsListHeight > 0 {
- tableHeight = min(tableHeight, CGFloat(maxResultsListHeight))
- }
-
- // Set a bottom margin of 10p
- if tableHeight < tableView.contentSize.height {
- tableHeight -= tableBottomMargin
- }
-
- var tableViewFrame = CGRect(x: 0, y: 0, width: frame.size.width - 4, height: tableHeight)
- tableViewFrame.origin = self.convert(tableViewFrame.origin, to: nil)
- tableViewFrame.origin.x += 2 + tableXOffset
- tableViewFrame.origin.y += frame.size.height + 2 + tableYOffset
- self.tableView?.frame.origin = tableViewFrame.origin // Avoid animating from (0, 0) when displaying at launch
- UIView.animate(withDuration: 0.2, animations: { [weak self] in
- self?.tableView?.frame = tableViewFrame
- })
-
- var shadowFrame = CGRect(x: 0, y: 0, width: frame.size.width - 6, height: 1)
- shadowFrame.origin = self.convert(shadowFrame.origin, to: nil)
- shadowFrame.origin.x += 3
- shadowFrame.origin.y = tableView.frame.origin.y
- shadowView!.frame = shadowFrame
- } else {
- let tableHeight = min((tableView.contentSize.height), (UIScreen.main.bounds.size.height - frame.origin.y - theme.cellHeight))
- UIView.animate(withDuration: 0.2, animations: { [weak self] in
- self?.tableView?.frame = CGRect(x: frame.origin.x + 2, y: (frame.origin.y - tableHeight), width: frame.size.width - 4, height: tableHeight)
- self?.shadowView?.frame = CGRect(x: frame.origin.x + 3, y: (frame.origin.y + 3), width: frame.size.width - 6, height: 1)
- })
- }
-
- superview?.bringSubviewToFront(tableView)
- superview?.bringSubviewToFront(shadowView!)
-
- if self.isFirstResponder {
- superview?.bringSubviewToFront(self)
- }
-
- tableView.layer.borderColor = theme.borderColor.cgColor
- tableView.layer.cornerRadius = tableCornerRadius
- tableView.separatorColor = theme.separatorColor
- tableView.backgroundColor = theme.bgColor
-
- tableView.reloadData()
- }
- }
-
- // Handle keyboard events
- @objc open func keyboardWillShow(_ notification: Notification) {
- if !keyboardIsShowing && isEditing {
- keyboardIsShowing = true
- keyboardFrame = ((notification as NSNotification).userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
- interactedWith = true
- prepareDrawTableResult()
- }
- }
-
- @objc open func keyboardWillHide(_ notification: Notification) {
- if keyboardIsShowing {
- keyboardIsShowing = false
- direction = .down
- redrawSearchTableView()
- }
- }
-
- @objc open func keyboardDidChangeFrame(_ notification: Notification) {
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
- self?.keyboardFrame = ((notification as NSNotification).userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
- self?.prepareDrawTableResult()
- }
- }
-
- @objc open func typingDidStop() {
- self.userStoppedTypingHandler?()
- }
-
- // Handle text field changes
- @objc open func textFieldDidChange() {
- if !inlineMode && tableView == nil {
- buildSearchTableView()
- }
-
- interactedWith = true
-
- // Detect pauses while typing
- timer?.invalidate()
- timer = Timer.scheduledTimer(timeInterval: typingStoppedDelay, target: self, selector: #selector(SearchTextField.typingDidStop), userInfo: self, repeats: false)
-
- if text!.isEmpty {
- clearResults()
- tableView?.reloadData()
- if startVisible || startVisibleWithoutInteraction {
- filter(forceShowAll: true)
- }
- self.placeholderLabel?.text = ""
- } else {
- filter(forceShowAll: forceNoFiltering)
- prepareDrawTableResult()
- }
-
- buildPlaceholderLabel()
- }
-
- @objc open func textFieldDidBeginEditing() {
- if (startVisible || startVisibleWithoutInteraction) && text!.isEmpty {
- clearResults()
- filter(forceShowAll: true)
- }
- placeholderLabel?.attributedText = nil
- }
-
- @objc open func textFieldDidEndEditing() {
- clearResults()
- tableView?.reloadData()
- placeholderLabel?.attributedText = nil
- }
-
- @objc open func textFieldDidEndEditingOnExit() {
- if let firstElement = filteredResults.first {
- if let itemSelectionHandler = self.itemSelectionHandler {
- itemSelectionHandler(filteredResults, 0)
- }
- else {
- if inlineMode, let filterAfter = startFilteringAfter {
- let stringElements = self.text?.components(separatedBy: filterAfter)
-
- self.text = stringElements!.first! + filterAfter + firstElement.title
- } else {
- self.text = firstElement.title
- }
- }
- }
- }
-
- open func hideResultsList() {
- if let tableFrame:CGRect = tableView?.frame {
- let newFrame = CGRect(x: tableFrame.origin.x, y: tableFrame.origin.y, width: tableFrame.size.width, height: 0.0)
- UIView.animate(withDuration: 0.2, animations: { [weak self] in
- self?.tableView?.frame = newFrame
- })
-
- }
- }
-
- fileprivate func filter(forceShowAll addAll: Bool) {
- clearResults()
-
- if text!.count < minCharactersNumberToStartFiltering {
- return
- }
-
- for i in 0 ..< filterDataSource.count {
-
- let item = filterDataSource[i]
-
- if !inlineMode {
- // Find text in title and subtitle
- let titleFilterRange = (item.title as NSString).range(of: text!, options: comparisonOptions)
- let subtitleFilterRange = item.subtitle != nil ? (item.subtitle! as NSString).range(of: text!, options: comparisonOptions) : NSMakeRange(NSNotFound, 0)
-
- if titleFilterRange.location != NSNotFound || subtitleFilterRange.location != NSNotFound || addAll {
- item.attributedTitle = NSMutableAttributedString(string: item.title)
- item.attributedSubtitle = NSMutableAttributedString(string: (item.subtitle != nil ? item.subtitle! : ""))
-
- item.attributedTitle!.setAttributes(highlightAttributes, range: titleFilterRange)
-
- if subtitleFilterRange.location != NSNotFound {
- item.attributedSubtitle!.setAttributes(highlightAttributesForSubtitle(), range: subtitleFilterRange)
- }
-
- filteredResults.append(item)
- }
- } else {
- var textToFilter = text!.lowercased()
-
- if inlineMode, let filterAfter = startFilteringAfter {
- if let suffixToFilter = textToFilter.components(separatedBy: filterAfter).last, (suffixToFilter != "" || startSuggestingImmediately == true), textToFilter != suffixToFilter {
- textToFilter = suffixToFilter
- } else {
- placeholderLabel?.text = ""
- return
- }
- }
-
- if item.title.lowercased().hasPrefix(textToFilter) {
- let indexFrom = textToFilter.index(textToFilter.startIndex, offsetBy: textToFilter.count)
- let itemSuffix = item.title[indexFrom...]
-
- item.attributedTitle = NSMutableAttributedString(string: String(itemSuffix))
- filteredResults.append(item)
- }
- }
- }
-
- tableView?.reloadData()
-
- if inlineMode {
- handleInlineFiltering()
- }
- }
-
- // Clean filtered results
- fileprivate func clearResults() {
- filteredResults.removeAll()
- tableView?.removeFromSuperview()
- }
-
- // Look for Font attribute, and if it exists, adapt to the subtitle font size
- fileprivate func highlightAttributesForSubtitle() -> [NSAttributedString.Key: AnyObject] {
- var highlightAttributesForSubtitle = [NSAttributedString.Key: AnyObject]()
-
- for attr in highlightAttributes {
- if attr.0 == NSAttributedString.Key.font {
- let fontName = (attr.1 as! UIFont).fontName
- let pointSize = (attr.1 as! UIFont).pointSize * fontConversionRate
- highlightAttributesForSubtitle[attr.0] = UIFont(name: fontName, size: pointSize)
- } else {
- highlightAttributesForSubtitle[attr.0] = attr.1
- }
- }
-
- return highlightAttributesForSubtitle
- }
-
- // Handle inline behaviour
- func handleInlineFiltering() {
- if let text = self.text {
- if text == "" {
- self.placeholderLabel?.attributedText = nil
- } else {
- if let firstResult = filteredResults.first {
- self.placeholderLabel?.attributedText = firstResult.attributedTitle
- } else {
- self.placeholderLabel?.attributedText = nil
- }
- }
- }
- }
-
- // MARK: - Prepare for draw table result
-
- fileprivate func prepareDrawTableResult() {
- guard let frame = self.superview?.convert(self.frame, to: UIApplication.shared.keyWindow) else { return }
- if let keyboardFrame = keyboardFrame {
- var newFrame = frame
- newFrame.size.height += theme.cellHeight
-
- if keyboardFrame.intersects(newFrame) {
- direction = .up
- } else {
- direction = .down
- }
-
- redrawSearchTableView()
- } else {
- if self.center.y + theme.cellHeight > UIApplication.shared.keyWindow!.frame.size.height {
- direction = .up
- } else {
- direction = .down
- }
- }
- }
-}
-
-extension SearchTextField: UITableViewDelegate, UITableViewDataSource {
- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- tableView.isHidden = !interactedWith || (filteredResults.count == 0)
- shadowView?.isHidden = !interactedWith || (filteredResults.count == 0)
-
- if maxNumberOfResults > 0 {
- return min(filteredResults.count, maxNumberOfResults)
- } else {
- return filteredResults.count
- }
- }
-
- public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- var cell = tableView.dequeueReusableCell(withIdentifier: SearchTextField.cellIdentifier)
-
- if cell == nil {
- cell = UITableViewCell(style: .subtitle, reuseIdentifier: SearchTextField.cellIdentifier)
- }
-
- cell!.backgroundColor = UIColor.clear
- cell!.layoutMargins = UIEdgeInsets.zero
- cell!.preservesSuperviewLayoutMargins = false
- cell!.textLabel?.font = theme.font
- cell!.detailTextLabel?.font = UIFont(name: theme.font.fontName, size: theme.font.pointSize * fontConversionRate)
- cell!.textLabel?.textColor = theme.fontColor
- cell!.detailTextLabel?.textColor = theme.subtitleFontColor
-
- cell!.textLabel?.text = filteredResults[(indexPath as NSIndexPath).row].title
- cell!.detailTextLabel?.text = filteredResults[(indexPath as NSIndexPath).row].subtitle
- cell!.textLabel?.attributedText = filteredResults[(indexPath as NSIndexPath).row].attributedTitle
- cell!.detailTextLabel?.attributedText = filteredResults[(indexPath as NSIndexPath).row].attributedSubtitle
-
- cell!.imageView?.image = filteredResults[(indexPath as NSIndexPath).row].image
-
- cell!.selectionStyle = .none
-
- return cell!
- }
-
- public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
- return theme.cellHeight
- }
-
- public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- if itemSelectionHandler == nil {
- self.text = filteredResults[(indexPath as NSIndexPath).row].title
- } else {
- let index = indexPath.row
- itemSelectionHandler!(filteredResults, index)
- }
-
- clearResults()
- }
-}
-
-////////////////////////////////////////////////////////////////////////
-// Search Text Field Theme
-
-public struct SearchTextFieldTheme {
- public var cellHeight: CGFloat
- public var bgColor: UIColor
- public var borderColor: UIColor
- public var borderWidth : CGFloat = 0
- public var separatorColor: UIColor
- public var font: UIFont
- public var fontColor: UIColor
- public var subtitleFontColor: UIColor
- public var placeholderColor: UIColor?
-
- init(cellHeight: CGFloat, bgColor:UIColor, borderColor: UIColor, separatorColor: UIColor, font: UIFont, fontColor: UIColor, subtitleFontColor: UIColor? = nil) {
- self.cellHeight = cellHeight
- self.borderColor = borderColor
- self.separatorColor = separatorColor
- self.bgColor = bgColor
- self.font = font
- self.fontColor = fontColor
- self.subtitleFontColor = subtitleFontColor ?? fontColor
- }
-
- public static func lightTheme() -> SearchTextFieldTheme {
- return SearchTextFieldTheme(cellHeight: 30, bgColor: UIColor (red: 1, green: 1, blue: 1, alpha: 0.6), borderColor: UIColor (red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0), separatorColor: UIColor.clear, font: UIFont.systemFont(ofSize: 10), fontColor: UIColor.black)
- }
-
- public static func darkTheme() -> SearchTextFieldTheme {
- return SearchTextFieldTheme(cellHeight: 30, bgColor: UIColor (red: 0.8, green: 0.8, blue: 0.8, alpha: 0.6), borderColor: UIColor (red: 0.7, green: 0.7, blue: 0.7, alpha: 1.0), separatorColor: UIColor.clear, font: UIFont.systemFont(ofSize: 10), fontColor: UIColor.white)
- }
-}
-
-////////////////////////////////////////////////////////////////////////
-// Filter Item
-
-open class SearchTextFieldItem {
- // Private vars
- fileprivate var attributedTitle: NSMutableAttributedString?
- fileprivate var attributedSubtitle: NSMutableAttributedString?
-
- // Public interface
- public var title: String
- public var subtitle: String?
- public var image: UIImage?
-
- public init(title: String, subtitle: String?, image: UIImage?) {
- self.title = title
- self.subtitle = subtitle
- self.image = image
- }
-
- public init(title: String, subtitle: String?) {
- self.title = title
- self.subtitle = subtitle
- }
-
- public init(title: String) {
- self.title = title
- }
-}
-
-public typealias SearchTextFieldItemHandler = (_ filteredResults: [SearchTextFieldItem], _ index: Int) -> Void
-
-////////////////////////////////////////////////////////////////////////
-// Suggestions List Direction
-
-enum Direction {
- case down
- case up
-}
diff --git a/SearchTextField/Assets/.gitkeep b/Sources/SearchTextField/Classes/.gitkeep
similarity index 100%
rename from SearchTextField/Assets/.gitkeep
rename to Sources/SearchTextField/Classes/.gitkeep
diff --git a/Sources/SearchTextField/Classes/SearchTextField.swift b/Sources/SearchTextField/Classes/SearchTextField.swift
new file mode 100755
index 0000000..458a248
--- /dev/null
+++ b/Sources/SearchTextField/Classes/SearchTextField.swift
@@ -0,0 +1,767 @@
+//
+// SearchTextField.swift
+// SearchTextField
+//
+// Created by Alejandro Pasccon on 4/20/16.
+// Copyright © 2016 Alejandro Pasccon. All rights reserved.
+//
+
+#if canImport(UIKit)
+import UIKit
+
+open class SearchTextField: UITextField {
+ ////////////////////////////////////////////////////////////////////////
+ // Public interface
+
+ /// Maximum number of results to be shown in the suggestions list
+ open var maxNumberOfResults = 0
+
+ /// Maximum height of the results list
+ open var maxResultsListHeight = 0
+
+ /// Indicate if this field has been interacted with yet
+ open var interactedWith = false
+
+ /// Indicate if keyboard is showing or not
+ open var keyboardIsShowing = false
+
+ /// How long to wait before deciding typing has stopped
+ open var typingStoppedDelay = 0.8
+
+ /// Set the visual theme
+ ///
+ /// Pre-defined Themes available:
+ /// - SearchTextFieldTheme.lightTheme()
+ /// - SearchTextFieldTheme.darkTheme()
+ open var theme = SearchTextFieldTheme.lightTheme() {
+ didSet {
+ tableView?.reloadData()
+
+ if let placeholderColor = theme.placeholderColor {
+ if let placeholderString = placeholder {
+ self.attributedPlaceholder = NSAttributedString(
+ string: placeholderString,
+ attributes: [NSAttributedString.Key.foregroundColor: placeholderColor]
+ )
+ }
+
+ self.placeholderLabel?.textColor = placeholderColor
+ }
+
+ if let hightlightedFont = self.highlightAttributes[.font] as? UIFont {
+ self.highlightAttributes[.font] = hightlightedFont.withSize(self.theme.font.pointSize)
+ }
+ }
+ }
+
+ /// Show the suggestions list without filter when the text field is focused
+ open var startVisible = false
+
+ /// Show the suggestions list without filter even if the text field is not focused
+ open var startVisibleWithoutInteraction = false {
+ didSet {
+ if startVisibleWithoutInteraction {
+ textFieldDidChange()
+ }
+ }
+ }
+
+ /// Set an array of SearchTextFieldItem's to be used for suggestions
+ open func filterItems(_ items: [SearchTextFieldItem]) {
+ filterDataSource = items
+ }
+
+ /// Set an array of strings to be used for suggestions
+ open func filterStrings(_ strings: [String]) {
+ var items = [SearchTextFieldItem]()
+
+ for value in strings {
+ items.append(SearchTextFieldItem(title: value))
+ }
+
+ filterItems(items)
+ }
+
+ /// Closure to handle when the user pick an item
+ open var itemSelectionHandler: SearchTextFieldItemHandler?
+
+ /// Closure to handle when the user stops typing
+ open var userStoppedTypingHandler: (() -> Void)?
+
+ /// Set your custom set of attributes in order to highlight the string found in each item
+ open var highlightAttributes: [NSAttributedString.Key: AnyObject] = [.font: UIFont.boldSystemFont(ofSize: 10)]
+
+ /// Start showing the default loading indicator, useful for searches that take some time.
+ open func showLoadingIndicator() {
+ rightViewMode = .always
+ indicator.startAnimating()
+ }
+
+ /// Force the results list to adapt to RTL languages
+ open var forceRightToLeft = false
+
+ /// Hide the default loading indicator
+ open func stopLoadingIndicator() {
+ rightViewMode = .never
+ indicator.stopAnimating()
+ }
+
+ /// When InlineMode is true, the suggestions appear in the same line than the entered string.
+ /// It's useful for email domains suggestion for example.
+ open var inlineMode: Bool = false {
+ didSet {
+ if inlineMode == true {
+ autocorrectionType = .no
+ spellCheckingType = .no
+ }
+ }
+ }
+
+ /// Only valid when InlineMode is true.
+ /// The suggestions appear after typing the provided string (or even better a character like '@')
+ open var startFilteringAfter: String?
+
+ /// Min number of characters to start filtering
+ open var minCharactersNumberToStartFiltering: Int = 0
+
+ /// Force no filtering (display the entire filtered data source)
+ open var forceNoFiltering: Bool = false
+
+ /// If startFilteringAfter is set,
+ /// and startSuggestingImmediately is true, the list of suggestions appear immediately
+ open var startSuggestingImmediately = false
+
+ /// Allow to decide the comparision options
+ open var comparisonOptions: NSString.CompareOptions = [.caseInsensitive]
+
+ /// Set the results list's header
+ open var resultsListHeader: UIView?
+
+ // Move the table around to customize for your layout
+ open var tableXOffset: CGFloat = 0.0
+ open var tableYOffset: CGFloat = 0.0
+ open var tableCornerRadius: CGFloat = 2.0
+ open var tableBottomMargin: CGFloat = 10.0
+
+ ////////////////////////////////////////////////////////////////////////
+ // Private implementation
+
+ fileprivate var tableView: UITableView?
+ fileprivate var shadowView: UIView?
+ fileprivate var direction: Direction = .down
+ fileprivate var fontConversionRate: CGFloat = 0.7
+ fileprivate var keyboardFrame: CGRect?
+ fileprivate var timer: Timer?
+ fileprivate var placeholderLabel: UILabel?
+ fileprivate static let cellIdentifier = "APSearchTextFieldCell"
+ fileprivate let indicator = UIActivityIndicatorView(style: .gray)
+ fileprivate var maxTableViewSize: CGFloat = 0
+
+ fileprivate var filteredResults = [SearchTextFieldItem]()
+ fileprivate var filterDataSource = [SearchTextFieldItem]() {
+ didSet {
+ filter(forceShowAll: forceNoFiltering)
+ buildSearchTableView()
+
+ if startVisibleWithoutInteraction {
+ textFieldDidChange()
+ }
+ }
+ }
+
+ fileprivate var currentInlineItem = ""
+
+ deinit {
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ override open func willMove(toWindow newWindow: UIWindow?) {
+ super.willMove(toWindow: newWindow)
+ tableView?.removeFromSuperview()
+ }
+
+ override open func willMove(toSuperview newSuperview: UIView?) {
+ super.willMove(toSuperview: newSuperview)
+
+ addTarget(self, action: #selector(SearchTextField.textFieldDidChange), for: .editingChanged)
+ addTarget(self, action: #selector(SearchTextField.textFieldDidBeginEditing), for: .editingDidBegin)
+ addTarget(self, action: #selector(SearchTextField.textFieldDidEndEditing), for: .editingDidEnd)
+ addTarget(self, action: #selector(SearchTextField.textFieldDidEndEditingOnExit), for: .editingDidEndOnExit)
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(SearchTextField.keyboardWillShow(_:)),
+ name: UIResponder.keyboardWillShowNotification, object: nil
+ )
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(SearchTextField.keyboardWillHide(_:)),
+ name: UIResponder.keyboardWillHideNotification, object: nil
+ )
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(SearchTextField.keyboardDidChangeFrame(_:)),
+ name: UIResponder.keyboardDidChangeFrameNotification, object: nil
+ )
+ }
+
+ override open func layoutSubviews() {
+ super.layoutSubviews()
+
+ if inlineMode {
+ buildPlaceholderLabel()
+ } else {
+ buildSearchTableView()
+ }
+
+ // Create the loading indicator
+ indicator.hidesWhenStopped = true
+ rightView = indicator
+ }
+
+ override open func rightViewRect(forBounds bounds: CGRect) -> CGRect {
+ var rightFrame = super.rightViewRect(forBounds: bounds)
+ rightFrame.origin.x -= 5
+ return rightFrame
+ }
+
+ // Create the filter table and shadow view
+ fileprivate func buildSearchTableView() {
+ guard let tableView = tableView, let shadowView = shadowView else {
+ self.tableView = UITableView(frame: CGRect.zero)
+ self.shadowView = UIView(frame: CGRect.zero)
+ buildSearchTableView()
+ return
+ }
+
+ tableView.layer.masksToBounds = true
+ tableView.layer.borderWidth = theme.borderWidth > 0 ? theme.borderWidth : 0.5
+ tableView.dataSource = self
+ tableView.delegate = self
+ tableView.separatorInset = UIEdgeInsets.zero
+ tableView.tableHeaderView = resultsListHeader
+ if forceRightToLeft {
+ tableView.semanticContentAttribute = .forceRightToLeft
+ }
+
+ shadowView.backgroundColor = UIColor.lightText
+ shadowView.layer.shadowColor = UIColor.black.cgColor
+ shadowView.layer.shadowOffset = CGSize.zero
+ shadowView.layer.shadowOpacity = 1
+
+ window?.addSubview(tableView)
+
+ redrawSearchTableView()
+ }
+
+ fileprivate func buildPlaceholderLabel() {
+ var newRect = placeholderRect(forBounds: bounds)
+ var caretRect = self.caretRect(for: beginningOfDocument)
+ let textRect = self.textRect(forBounds: bounds)
+
+ if let range = textRange(from: beginningOfDocument, to: endOfDocument) {
+ caretRect = firstRect(for: range)
+ }
+
+ newRect.origin.x = caretRect.origin.x + caretRect.size.width + textRect.origin.x
+ newRect.size.width -= newRect.origin.x
+
+ if let placeholderLabel = placeholderLabel {
+ placeholderLabel.font = font
+ placeholderLabel.frame = newRect
+ } else {
+ placeholderLabel = UILabel(frame: newRect)
+ placeholderLabel?.font = font
+ placeholderLabel?.backgroundColor = UIColor.clear
+ placeholderLabel?.lineBreakMode = .byClipping
+
+ if let placeholderColor = attributedPlaceholder?
+ .attribute(NSAttributedString.Key.foregroundColor, at: 0, effectiveRange: nil) as? UIColor {
+ placeholderLabel?.textColor = placeholderColor
+ } else {
+ placeholderLabel?.textColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1.0)
+ }
+
+ addSubview(placeholderLabel!)
+ }
+ }
+
+ // Re-set frames and theme colors
+ fileprivate func redrawSearchTableView() {
+ if inlineMode {
+ tableView?.isHidden = true
+ return
+ }
+
+ if let tableView = tableView {
+ guard let frame = superview?.convert(self.frame, to: nil) else { return }
+
+ // TableViews use estimated cell heights to calculate content size until they
+ // are on-screen. We must set this to the theme cell height to avoid getting an
+ // incorrect contentSize when we have specified non-standard fonts and/or
+ // cellHeights in the theme. We do it here to ensure updates to these settings
+ // are recognized if changed after the tableView is created
+ tableView.estimatedRowHeight = theme.cellHeight
+ if direction == .down {
+ var tableHeight: CGFloat = 0
+ if keyboardIsShowing, let keyboardHeight = keyboardFrame?.size.height {
+ tableHeight = min(
+ tableView.contentSize.height,
+ UIScreen.main.bounds.size.height - frame.origin.y - frame.height - keyboardHeight
+ )
+ } else {
+ tableHeight = min(
+ tableView.contentSize.height,
+ UIScreen.main.bounds.size.height - frame.origin.y - frame.height
+ )
+ }
+
+ if maxResultsListHeight > 0 {
+ tableHeight = min(tableHeight, CGFloat(maxResultsListHeight))
+ }
+
+ // Set a bottom margin of 10p
+ if tableHeight < tableView.contentSize.height {
+ tableHeight -= tableBottomMargin
+ }
+
+ var tableViewFrame = CGRect(x: 0, y: 0, width: frame.size.width - 4, height: tableHeight)
+ tableViewFrame.origin = convert(tableViewFrame.origin, to: nil)
+ tableViewFrame.origin.x += 2 + tableXOffset
+ tableViewFrame.origin.y += frame.size.height + 2 + tableYOffset
+
+ // Avoid animating from (0, 0) when displaying at launch
+ self.tableView?.frame.origin = tableViewFrame.origin
+
+ UIView.animate(withDuration: 0.2, animations: { [weak self] in
+ self?.tableView?.frame = tableViewFrame
+ })
+
+ var shadowFrame = CGRect(x: 0, y: 0, width: frame.size.width - 6, height: 1)
+ shadowFrame.origin = convert(shadowFrame.origin, to: nil)
+ shadowFrame.origin.x += 3
+ shadowFrame.origin.y = tableView.frame.origin.y
+ shadowView!.frame = shadowFrame
+ } else {
+ let tableHeight = min(
+ tableView.contentSize.height,
+ UIScreen.main.bounds.size.height - frame.origin.y - theme.cellHeight
+ )
+
+ UIView.animate(withDuration: 0.2, animations: { [weak self] in
+ self?.tableView?.frame = CGRect(
+ x: frame.origin.x + 2,
+ y: frame.origin.y - tableHeight, width: frame.size.width - 4, height: tableHeight
+ )
+
+ self?.shadowView?.frame = CGRect(
+ x: frame.origin.x + 3, y: frame.origin.y + 3,
+ width: frame.size.width - 6, height: 1
+ )
+ })
+ }
+
+ superview?.bringSubviewToFront(tableView)
+ superview?.bringSubviewToFront(shadowView!)
+
+ if isFirstResponder {
+ superview?.bringSubviewToFront(self)
+ }
+
+ tableView.layer.borderColor = theme.borderColor.cgColor
+ tableView.layer.cornerRadius = tableCornerRadius
+ tableView.separatorColor = theme.separatorColor
+ tableView.backgroundColor = theme.bgColor
+
+ tableView.reloadData()
+ }
+ }
+
+ // Handle keyboard events
+ @objc open func keyboardWillShow(_ notification: Notification) {
+ guard let userInfo = (notification as NSNotification).userInfo,
+ let keyboardFrameInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
+
+ if !keyboardIsShowing, isEditing {
+ keyboardIsShowing = true
+ keyboardFrame = keyboardFrameInfo.cgRectValue
+ interactedWith = true
+ prepareDrawTableResult()
+ }
+ }
+
+ @objc open func keyboardWillHide(_: Notification) {
+ if keyboardIsShowing {
+ keyboardIsShowing = false
+ direction = .down
+ redrawSearchTableView()
+ }
+ }
+
+ @objc open func keyboardDidChangeFrame(_ notification: Notification) {
+ guard let userInfo = (notification as NSNotification).userInfo,
+ let keyboardFrameInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
+ self?.keyboardFrame = keyboardFrameInfo.cgRectValue
+ self?.prepareDrawTableResult()
+ }
+ }
+
+ @objc open func typingDidStop() {
+ userStoppedTypingHandler?()
+ }
+
+ // Handle text field changes
+ @objc open func textFieldDidChange() {
+ if !inlineMode, tableView == nil {
+ buildSearchTableView()
+ }
+
+ interactedWith = true
+
+ // Detect pauses while typing
+ timer?.invalidate()
+ timer = Timer.scheduledTimer(
+ timeInterval: typingStoppedDelay,
+ target: self,
+ selector: #selector(SearchTextField.typingDidStop),
+ userInfo: self,
+ repeats: false
+ )
+
+ if text!.isEmpty {
+ clearResults()
+ tableView?.reloadData()
+ if startVisible || startVisibleWithoutInteraction {
+ filter(forceShowAll: true)
+ }
+ placeholderLabel?.text = ""
+ } else {
+ filter(forceShowAll: forceNoFiltering)
+ prepareDrawTableResult()
+ }
+
+ buildPlaceholderLabel()
+ }
+
+ @objc open func textFieldDidBeginEditing() {
+ if startVisible || startVisibleWithoutInteraction, text!.isEmpty {
+ clearResults()
+ filter(forceShowAll: true)
+ }
+ placeholderLabel?.attributedText = nil
+ }
+
+ @objc open func textFieldDidEndEditing() {
+ clearResults()
+ tableView?.reloadData()
+ placeholderLabel?.attributedText = nil
+ }
+
+ @objc open func textFieldDidEndEditingOnExit() {
+ if let firstElement = filteredResults.first {
+ if let itemSelectionHandler = self.itemSelectionHandler {
+ itemSelectionHandler(filteredResults, 0)
+ } else {
+ if inlineMode, let filterAfter = startFilteringAfter {
+ let stringElements = text?.components(separatedBy: filterAfter)
+
+ text = stringElements!.first! + filterAfter + firstElement.title
+ } else {
+ text = firstElement.title
+ }
+ }
+ }
+ }
+
+ open func hideResultsList() {
+ if let tableFrame: CGRect = tableView?.frame {
+ let newFrame = CGRect(
+ x: tableFrame.origin.x,
+ y: tableFrame.origin.y,
+ width: tableFrame.size.width,
+ height: 0.0
+ )
+
+ UIView.animate(withDuration: 0.2, animations: { [weak self] in
+ self?.tableView?.frame = newFrame
+ })
+ }
+ }
+
+ fileprivate func filter(forceShowAll addAll: Bool) {
+ clearResults()
+
+ if text!.count < minCharactersNumberToStartFiltering {
+ return
+ }
+
+ for idx in 0 ..< filterDataSource.count {
+ let item = filterDataSource[idx]
+
+ if !inlineMode {
+ // Find text in title and subtitle
+ let titleFilterRange = (item.title as NSString).range(of: text!, options: comparisonOptions)
+ let subtitleFilterRange = item.subtitle != nil
+ ? (item.subtitle! as NSString).range(of: text!, options: comparisonOptions)
+ : NSRange(location: NSNotFound, length: 0)
+
+ if titleFilterRange.location != NSNotFound || subtitleFilterRange.location != NSNotFound || addAll {
+ item.attributedTitle = NSMutableAttributedString(string: item.title)
+ item.attributedSubtitle = NSMutableAttributedString(string: item.subtitle != nil ? item.subtitle! : "")
+
+ item.attributedTitle!.setAttributes(highlightAttributes, range: titleFilterRange)
+
+ if subtitleFilterRange.location != NSNotFound {
+ item.attributedSubtitle!.setAttributes(highlightAttributesForSubtitle(), range: subtitleFilterRange)
+ }
+
+ filteredResults.append(item)
+ }
+ } else {
+ var textToFilter = text!.lowercased()
+
+ if inlineMode, let filterAfter = startFilteringAfter {
+ if let suffixToFilter = textToFilter.components(separatedBy: filterAfter).last,
+ suffixToFilter != "" || startSuggestingImmediately == true,
+ textToFilter != suffixToFilter {
+ textToFilter = suffixToFilter
+ } else {
+ placeholderLabel?.text = ""
+ return
+ }
+ }
+
+ if item.title.lowercased().hasPrefix(textToFilter) {
+ let indexFrom = textToFilter.index(textToFilter.startIndex, offsetBy: textToFilter.count)
+ let itemSuffix = item.title[indexFrom...]
+
+ item.attributedTitle = NSMutableAttributedString(string: String(itemSuffix))
+ filteredResults.append(item)
+ }
+ }
+ }
+
+ tableView?.reloadData()
+
+ if inlineMode {
+ handleInlineFiltering()
+ }
+ }
+
+ // Clean filtered results
+ fileprivate func clearResults() {
+ filteredResults.removeAll()
+ tableView?.removeFromSuperview()
+ }
+
+ // Look for Font attribute, and if it exists, adapt to the subtitle font size
+ fileprivate func highlightAttributesForSubtitle() -> [NSAttributedString.Key: AnyObject] {
+ var highlightAttributesForSubtitle = [NSAttributedString.Key: AnyObject]()
+
+ for attr in highlightAttributes {
+ if attr.0 == NSAttributedString.Key.font, let font = attr.1 as? UIFont {
+ let fontName = font.fontName
+ let pointSize = font.pointSize * fontConversionRate
+ highlightAttributesForSubtitle[attr.0] = UIFont(name: fontName, size: pointSize)
+ } else {
+ highlightAttributesForSubtitle[attr.0] = attr.1
+ }
+ }
+
+ return highlightAttributesForSubtitle
+ }
+
+ // Handle inline behaviour
+ func handleInlineFiltering() {
+ if let text = self.text {
+ if text == "" {
+ placeholderLabel?.attributedText = nil
+ } else {
+ if let firstResult = filteredResults.first {
+ placeholderLabel?.attributedText = firstResult.attributedTitle
+ } else {
+ placeholderLabel?.attributedText = nil
+ }
+ }
+ }
+ }
+
+ // MARK: - Prepare for draw table result
+
+ fileprivate func prepareDrawTableResult() {
+ guard let frame = superview?.convert(self.frame, to: UIApplication.shared.keyWindow) else { return }
+ if let keyboardFrame = keyboardFrame {
+ var newFrame = frame
+ newFrame.size.height += theme.cellHeight
+
+ if keyboardFrame.intersects(newFrame) {
+ direction = .up
+ } else {
+ direction = .down
+ }
+
+ redrawSearchTableView()
+ } else {
+ if center.y + theme.cellHeight > UIApplication.shared.keyWindow!.frame.size.height {
+ direction = .up
+ } else {
+ direction = .down
+ }
+ }
+ }
+}
+
+extension SearchTextField: UITableViewDelegate, UITableViewDataSource {
+ public func tableView(_ tableView: UITableView, numberOfRowsInSection _: Int) -> Int {
+ tableView.isHidden = !interactedWith || (filteredResults.count == 0)
+ shadowView?.isHidden = !interactedWith || (filteredResults.count == 0)
+
+ if maxNumberOfResults > 0 {
+ return min(filteredResults.count, maxNumberOfResults)
+ } else {
+ return filteredResults.count
+ }
+ }
+
+ public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ guard indexPath.row < filteredResults.count else {
+ return UITableViewCell(style: .subtitle, reuseIdentifier: SearchTextField.cellIdentifier)
+ }
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: SearchTextField.cellIdentifier)
+ ?? UITableViewCell(style: .subtitle, reuseIdentifier: SearchTextField.cellIdentifier)
+
+ cell.backgroundColor = UIColor.clear
+ cell.layoutMargins = UIEdgeInsets.zero
+ cell.preservesSuperviewLayoutMargins = false
+ cell.textLabel?.font = theme.font
+ cell.detailTextLabel?.font = UIFont(name: theme.font.fontName, size: theme.font.pointSize * fontConversionRate)
+ cell.textLabel?.textColor = theme.fontColor
+ cell.detailTextLabel?.textColor = theme.subtitleFontColor
+
+ cell.textLabel?.text = filteredResults[indexPath.row].title
+ cell.detailTextLabel?.text = filteredResults[indexPath.row].subtitle
+ cell.textLabel?.attributedText = filteredResults[indexPath.row].attributedTitle
+ cell.detailTextLabel?.attributedText = filteredResults[indexPath.row].attributedSubtitle
+
+ cell.imageView?.image = filteredResults[indexPath.row].image
+
+ cell.selectionStyle = .none
+
+ return cell
+ }
+
+ public func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat {
+ theme.cellHeight
+ }
+
+ public func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+ if itemSelectionHandler == nil {
+ text = filteredResults[(indexPath as NSIndexPath).row].title
+ } else {
+ let index = indexPath.row
+ itemSelectionHandler!(filteredResults, index)
+ }
+
+ clearResults()
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Search Text Field Theme
+
+public struct SearchTextFieldTheme {
+ public var cellHeight: CGFloat
+ public var bgColor: UIColor
+ public var borderColor: UIColor
+ public var borderWidth: CGFloat = 0
+ public var separatorColor: UIColor
+ public var font: UIFont
+ public var fontColor: UIColor
+ public var subtitleFontColor: UIColor
+ public var placeholderColor: UIColor?
+
+ init(
+ cellHeight: CGFloat,
+ bgColor: UIColor,
+ borderColor: UIColor,
+ separatorColor: UIColor,
+ font: UIFont,
+ fontColor: UIColor,
+ subtitleFontColor: UIColor? = nil
+ ) {
+ self.cellHeight = cellHeight
+ self.borderColor = borderColor
+ self.separatorColor = separatorColor
+ self.bgColor = bgColor
+ self.font = font
+ self.fontColor = fontColor
+ self.subtitleFontColor = subtitleFontColor ?? fontColor
+ }
+
+ public static func lightTheme() -> SearchTextFieldTheme {
+ SearchTextFieldTheme(
+ cellHeight: 30,
+ bgColor: UIColor(red: 1, green: 1, blue: 1, alpha: 0.6),
+ borderColor: UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0),
+ separatorColor: UIColor.clear,
+ font: UIFont.systemFont(ofSize: 10),
+ fontColor: UIColor.black
+ )
+ }
+
+ public static func darkTheme() -> SearchTextFieldTheme {
+ SearchTextFieldTheme(
+ cellHeight: 30,
+ bgColor: UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 0.6),
+ borderColor: UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1.0),
+ separatorColor: UIColor.clear,
+ font: UIFont.systemFont(ofSize: 10),
+ fontColor: UIColor.white
+ )
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Filter Item
+
+open class SearchTextFieldItem {
+ // Private vars
+ fileprivate var attributedTitle: NSMutableAttributedString?
+ fileprivate var attributedSubtitle: NSMutableAttributedString?
+
+ // Public interface
+ public var title: String
+ public var subtitle: String?
+ public var image: UIImage?
+
+ public init(title: String, subtitle: String?, image: UIImage?) {
+ self.title = title
+ self.subtitle = subtitle
+ self.image = image
+ }
+
+ public init(title: String, subtitle: String?) {
+ self.title = title
+ self.subtitle = subtitle
+ }
+
+ public init(title: String) {
+ self.title = title
+ }
+}
+
+public typealias SearchTextFieldItemHandler = (_ filteredResults: [SearchTextFieldItem], _ index: Int) -> Void
+
+////////////////////////////////////////////////////////////////////////
+// Suggestions List Direction
+
+enum Direction {
+ case down
+ case up
+}
+#endif
diff --git a/Tests/SearchTextFieldTests/SearchTextFieldTest.swift b/Tests/SearchTextFieldTests/SearchTextFieldTest.swift
new file mode 100644
index 0000000..b82a369
--- /dev/null
+++ b/Tests/SearchTextFieldTests/SearchTextFieldTest.swift
@@ -0,0 +1,25 @@
+//
+// SearchTextFieldTest.swift
+//
+//
+// Created by Brent Mifsud on 2020-05-15.
+//
+
+#if canImport(UIKit)
+ @testable import SearchTextField
+ import XCTest
+
+ class SearchTextFieldTest: XCTestCase {
+ override func setUp() {
+ // add setup code
+ }
+
+ override func tearDown() {
+ // add tear down code
+ }
+
+ func testSearchTextField() {
+ // add test code
+ }
+ }
+#endif