├── .gitignore ├── .travis.yml ├── Framework ├── Info.plist └── InputAssistant.h ├── InputAssistant.podspec ├── InputAssistant.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── InputAssistant Sample.xcscheme │ └── InputAssistant.xcscheme ├── LICENSE ├── README.md ├── Resources ├── Keyboard.png └── Keyboard_iPad.png ├── Sample ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Down.imageset │ │ ├── Contents.json │ │ └── down.pdf ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist └── ViewController.swift ├── Sources ├── InputAssistantCollectionView.swift └── InputAssistantView.swift └── build.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.xcworkspacedata 3 | *.xcuserdatad 4 | Carthage/ 5 | Pods/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode11.2 3 | env: 4 | global: 5 | - LC_CTYPE=en_US.UTF-8 6 | - LANG=en_US.UTF-8 7 | - PROJECT=InputAssistant.xcodeproj 8 | - IOS_FRAMEWORK_SCHEME="InputAssistant" 9 | - IOS_SIM_SDK=iphonesimulator13.2 10 | - IOS_DEV_SDK=iphoneos13.2 11 | matrix: 12 | - SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SIM_SDK" 13 | - SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_DEV_SDK" 14 | script: 15 | - set -o pipefail 16 | - xcodebuild -version 17 | - xcodebuild -showsdks 18 | - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES build; 19 | 20 | branches: 21 | only: 22 | - master 23 | -------------------------------------------------------------------------------- /Framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Framework/InputAssistant.h: -------------------------------------------------------------------------------- 1 | // 2 | // InputAssistant.h 3 | // InputAssistant 4 | // 5 | // Created by Ian McDowell on 1/28/18. 6 | // Copyright © 2018 Ian McDowell. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for InputAssistant. 12 | FOUNDATION_EXPORT double InputAssistantVersionNumber; 13 | 14 | //! Project version string for InputAssistant. 15 | FOUNDATION_EXPORT const unsigned char InputAssistantVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /InputAssistant.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint InputAssistant.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "InputAssistant" 19 | s.version = "1.0.3" 20 | s.summary = "This library is a view that shows custom auto-complete suggestions for your UITextField / UITextView." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | s.description = <<-DESC 28 | InputAssistant 1.0.3 - This library is a view that shows custom auto-complete suggestions for your UITextField / UITextView. 29 | DESC 30 | 31 | s.homepage = "https://github.com/IMcD23/InputAssistant" 32 | s.screenshots = "https://github.com/IMcD23/InputAssistant/raw/master/Resources/Keyboard.png","https://github.com/IMcD23/InputAssistant/raw/master/Resources/Keyboard_iPad.png" 33 | 34 | 35 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 36 | # 37 | # Licensing your code is important. See http://choosealicense.com for more info. 38 | # CocoaPods will detect a license file if there is a named LICENSE* 39 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 40 | # 41 | 42 | s.license = { :type => "MIT", :file => "LICENSE" } 43 | 44 | 45 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 46 | # 47 | # Specify the authors of the library, with email addresses. Email addresses 48 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 49 | # accepts just a name if you'd rather not provide an email address. 50 | # 51 | # Specify a social_media_url where others can refer to, for example a twitter 52 | # profile URL. 53 | # 54 | 55 | s.author = { "Ian McDowell" => "me@ianmcdowell.net" } 56 | # Or just: s.author = "Ian McDowell" 57 | # s.authors = { "Ian McDowell" => "me@ianmcdowell.net" } 58 | s.social_media_url = "http://twitter.com/ian_mcdowell" 59 | 60 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 61 | # 62 | # If this Pod runs only on iOS or OS X, then specify the platform and 63 | # the deployment target. You can optionally include the target after the platform. 64 | # 65 | 66 | s.platform = :ios, "10.0" 67 | # s.platform = :ios, "5.0" 68 | 69 | # When using multiple platforms 70 | # s.ios.deployment_target = "5.0" 71 | # s.osx.deployment_target = "10.7" 72 | # s.watchos.deployment_target = "2.0" 73 | # s.tvos.deployment_target = "9.0" 74 | 75 | 76 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 77 | # 78 | # Specify the location from where the source should be retrieved. 79 | # Supports git, hg, bzr, svn and HTTP. 80 | # 81 | 82 | s.source = { :git => "https://github.com/IMcD23/InputAssistant.git", :tag => "#{s.version}" } 83 | 84 | 85 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 86 | # 87 | # CocoaPods is smart about how it includes source code. For source files 88 | # giving a folder will include any swift, h, m, mm, c & cpp files. 89 | # For header files it will include any header in the folder. 90 | # Not including the public_header_files will make all headers public. 91 | # 92 | 93 | s.source_files = "Sources/**/*.swift" 94 | 95 | 96 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 97 | # 98 | # A list of resources included with the Pod. These are copied into the 99 | # target bundle with a build phase script. Anything else will be cleaned. 100 | # You can preserve files from being cleaned, please don't preserve 101 | # non-essential files like tests, examples and documentation. 102 | # 103 | 104 | # s.resource = "icon.png" 105 | # s.resources = "Resources/*.png" 106 | 107 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 108 | 109 | 110 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 111 | # 112 | # Link your library with frameworks, or libraries. Libraries do not include 113 | # the lib prefix of their name. 114 | # 115 | 116 | # s.framework = "SomeFramework" 117 | # s.frameworks = "SomeFramework", "AnotherFramework" 118 | 119 | # s.library = "iconv" 120 | # s.libraries = "iconv", "xml2" 121 | 122 | 123 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 124 | # 125 | # If your library depends on compiler flags you can set them in the xcconfig hash 126 | # where they will only apply to your library. If you depend on other Podspecs 127 | # you can include multiple dependencies to ensure it works. 128 | 129 | s.requires_arc = true 130 | s.swift_version = "5" 131 | 132 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 133 | # s.dependency "JSONKit", "~> 1.4" 134 | 135 | end 136 | -------------------------------------------------------------------------------- /InputAssistant.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3C2E4353201E7FA700E4254A /* InputAssistant.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C2E4352201E7FA700E4254A /* InputAssistant.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 3C2E4356201E801400E4254A /* InputAssistantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2E4355201E801400E4254A /* InputAssistantView.swift */; }; 12 | 3C2E435E201E883E00E4254A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2E435D201E883E00E4254A /* AppDelegate.swift */; }; 13 | 3C2E4360201E883E00E4254A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2E435F201E883E00E4254A /* ViewController.swift */; }; 14 | 3C2E4365201E883E00E4254A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3C2E4364201E883E00E4254A /* Assets.xcassets */; }; 15 | 3C2E4368201E883E00E4254A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3C2E4366201E883E00E4254A /* LaunchScreen.storyboard */; }; 16 | 3C2E4370201E8A4C00E4254A /* InputAssistant.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 3C2E4347201E7E6C00E4254A /* InputAssistant.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 3C2E4372201E8C2A00E4254A /* InputAssistantCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2E4371201E8C2A00E4254A /* InputAssistantCollectionView.swift */; }; 18 | 3C2E4382201EFAD800E4254A /* InputAssistantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2E4355201E801400E4254A /* InputAssistantView.swift */; }; 19 | 3C2E4383201EFAD800E4254A /* InputAssistantCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2E4371201E8C2A00E4254A /* InputAssistantCollectionView.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 3C2E436D201E8A4200E4254A /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 3C2E433E201E7E6C00E4254A /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 3C2E4346201E7E6C00E4254A; 28 | remoteInfo = InputAssistant; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | 3C2E436F201E8A4500E4254A /* Copy Frameworks */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = ""; 37 | dstSubfolderSpec = 10; 38 | files = ( 39 | 3C2E4370201E8A4C00E4254A /* InputAssistant.framework in Copy Frameworks */, 40 | ); 41 | name = "Copy Frameworks"; 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 3C2E4347201E7E6C00E4254A /* InputAssistant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = InputAssistant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 3C2E434B201E7E6C00E4254A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 3C2E4352201E7FA700E4254A /* InputAssistant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InputAssistant.h; sourceTree = ""; }; 50 | 3C2E4355201E801400E4254A /* InputAssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputAssistantView.swift; sourceTree = ""; }; 51 | 3C2E435B201E883E00E4254A /* InputAssistant Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "InputAssistant Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 3C2E435D201E883E00E4254A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 53 | 3C2E435F201E883E00E4254A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 54 | 3C2E4364201E883E00E4254A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 3C2E4367201E883E00E4254A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 3C2E4369201E883E00E4254A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 3C2E4371201E8C2A00E4254A /* InputAssistantCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputAssistantCollectionView.swift; sourceTree = ""; }; 58 | 3C2E4379201EFACD00E4254A /* liblibInputAssistant.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblibInputAssistant.a; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 3C2E4343201E7E6C00E4254A /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 3C2E4358201E883E00E4254A /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | 3C2E4376201EFACD00E4254A /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXFrameworksBuildPhase section */ 84 | 85 | /* Begin PBXGroup section */ 86 | 3C2E433D201E7E6C00E4254A = { 87 | isa = PBXGroup; 88 | children = ( 89 | 3C2E4354201E7FEE00E4254A /* Sources */, 90 | 3C2E4349201E7E6C00E4254A /* Framework */, 91 | 3C2E435C201E883E00E4254A /* Sample */, 92 | 3C2E4348201E7E6C00E4254A /* Products */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 3C2E4348201E7E6C00E4254A /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 3C2E4347201E7E6C00E4254A /* InputAssistant.framework */, 100 | 3C2E435B201E883E00E4254A /* InputAssistant Sample.app */, 101 | 3C2E4379201EFACD00E4254A /* liblibInputAssistant.a */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | 3C2E4349201E7E6C00E4254A /* Framework */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 3C2E4352201E7FA700E4254A /* InputAssistant.h */, 110 | 3C2E434B201E7E6C00E4254A /* Info.plist */, 111 | ); 112 | path = Framework; 113 | sourceTree = ""; 114 | }; 115 | 3C2E4354201E7FEE00E4254A /* Sources */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 3C2E4355201E801400E4254A /* InputAssistantView.swift */, 119 | 3C2E4371201E8C2A00E4254A /* InputAssistantCollectionView.swift */, 120 | ); 121 | path = Sources; 122 | sourceTree = ""; 123 | }; 124 | 3C2E435C201E883E00E4254A /* Sample */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 3C2E435D201E883E00E4254A /* AppDelegate.swift */, 128 | 3C2E435F201E883E00E4254A /* ViewController.swift */, 129 | 3C2E4364201E883E00E4254A /* Assets.xcassets */, 130 | 3C2E4366201E883E00E4254A /* LaunchScreen.storyboard */, 131 | 3C2E4369201E883E00E4254A /* Info.plist */, 132 | ); 133 | path = Sample; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXHeadersBuildPhase section */ 139 | 3C2E4344201E7E6C00E4254A /* Headers */ = { 140 | isa = PBXHeadersBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 3C2E4353201E7FA700E4254A /* InputAssistant.h in Headers */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXHeadersBuildPhase section */ 148 | 149 | /* Begin PBXNativeTarget section */ 150 | 3C2E4346201E7E6C00E4254A /* InputAssistant */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = 3C2E434F201E7E6C00E4254A /* Build configuration list for PBXNativeTarget "InputAssistant" */; 153 | buildPhases = ( 154 | 3C2E4342201E7E6C00E4254A /* Sources */, 155 | 3C2E4343201E7E6C00E4254A /* Frameworks */, 156 | 3C2E4344201E7E6C00E4254A /* Headers */, 157 | 3C2E4345201E7E6C00E4254A /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = InputAssistant; 164 | productName = InputAssistant; 165 | productReference = 3C2E4347201E7E6C00E4254A /* InputAssistant.framework */; 166 | productType = "com.apple.product-type.framework"; 167 | }; 168 | 3C2E435A201E883E00E4254A /* InputAssistant Sample */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 3C2E436A201E883E00E4254A /* Build configuration list for PBXNativeTarget "InputAssistant Sample" */; 171 | buildPhases = ( 172 | 3C2E4357201E883E00E4254A /* Sources */, 173 | 3C2E4358201E883E00E4254A /* Frameworks */, 174 | 3C2E4359201E883E00E4254A /* Resources */, 175 | 3C2E436F201E8A4500E4254A /* Copy Frameworks */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | 3C2E436E201E8A4200E4254A /* PBXTargetDependency */, 181 | ); 182 | name = "InputAssistant Sample"; 183 | productName = "InputAssistant Sample"; 184 | productReference = 3C2E435B201E883E00E4254A /* InputAssistant Sample.app */; 185 | productType = "com.apple.product-type.application"; 186 | }; 187 | 3C2E4378201EFACD00E4254A /* libInputAssistant */ = { 188 | isa = PBXNativeTarget; 189 | buildConfigurationList = 3C2E437F201EFACD00E4254A /* Build configuration list for PBXNativeTarget "libInputAssistant" */; 190 | buildPhases = ( 191 | 3C2E4375201EFACD00E4254A /* Sources */, 192 | 3C2E4376201EFACD00E4254A /* Frameworks */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = libInputAssistant; 199 | productName = libInputAssistant; 200 | productReference = 3C2E4379201EFACD00E4254A /* liblibInputAssistant.a */; 201 | productType = "com.apple.product-type.library.static"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | 3C2E433E201E7E6C00E4254A /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastSwiftUpdateCheck = 0920; 210 | LastUpgradeCheck = 1010; 211 | ORGANIZATIONNAME = "Ian McDowell"; 212 | TargetAttributes = { 213 | 3C2E4346201E7E6C00E4254A = { 214 | CreatedOnToolsVersion = 9.2; 215 | LastSwiftMigration = 1020; 216 | }; 217 | 3C2E435A201E883E00E4254A = { 218 | CreatedOnToolsVersion = 9.2; 219 | LastSwiftMigration = 1020; 220 | }; 221 | 3C2E4378201EFACD00E4254A = { 222 | CreatedOnToolsVersion = 9.2; 223 | LastSwiftMigration = 1020; 224 | }; 225 | }; 226 | }; 227 | buildConfigurationList = 3C2E4341201E7E6C00E4254A /* Build configuration list for PBXProject "InputAssistant" */; 228 | compatibilityVersion = "Xcode 10.0"; 229 | developmentRegion = en; 230 | hasScannedForEncodings = 0; 231 | knownRegions = ( 232 | en, 233 | Base, 234 | ); 235 | mainGroup = 3C2E433D201E7E6C00E4254A; 236 | productRefGroup = 3C2E4348201E7E6C00E4254A /* Products */; 237 | projectDirPath = ""; 238 | projectRoot = ""; 239 | targets = ( 240 | 3C2E4346201E7E6C00E4254A /* InputAssistant */, 241 | 3C2E4378201EFACD00E4254A /* libInputAssistant */, 242 | 3C2E435A201E883E00E4254A /* InputAssistant Sample */, 243 | ); 244 | }; 245 | /* End PBXProject section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | 3C2E4345201E7E6C00E4254A /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | 3C2E4359201E883E00E4254A /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 3C2E4368201E883E00E4254A /* LaunchScreen.storyboard in Resources */, 260 | 3C2E4365201E883E00E4254A /* Assets.xcassets in Resources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXResourcesBuildPhase section */ 265 | 266 | /* Begin PBXSourcesBuildPhase section */ 267 | 3C2E4342201E7E6C00E4254A /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 3C2E4372201E8C2A00E4254A /* InputAssistantCollectionView.swift in Sources */, 272 | 3C2E4356201E801400E4254A /* InputAssistantView.swift in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 3C2E4357201E883E00E4254A /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 3C2E4360201E883E00E4254A /* ViewController.swift in Sources */, 281 | 3C2E435E201E883E00E4254A /* AppDelegate.swift in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 3C2E4375201EFACD00E4254A /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 3C2E4383201EFAD800E4254A /* InputAssistantCollectionView.swift in Sources */, 290 | 3C2E4382201EFAD800E4254A /* InputAssistantView.swift in Sources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXSourcesBuildPhase section */ 295 | 296 | /* Begin PBXTargetDependency section */ 297 | 3C2E436E201E8A4200E4254A /* PBXTargetDependency */ = { 298 | isa = PBXTargetDependency; 299 | target = 3C2E4346201E7E6C00E4254A /* InputAssistant */; 300 | targetProxy = 3C2E436D201E8A4200E4254A /* PBXContainerItemProxy */; 301 | }; 302 | /* End PBXTargetDependency section */ 303 | 304 | /* Begin PBXVariantGroup section */ 305 | 3C2E4366201E883E00E4254A /* LaunchScreen.storyboard */ = { 306 | isa = PBXVariantGroup; 307 | children = ( 308 | 3C2E4367201E883E00E4254A /* Base */, 309 | ); 310 | name = LaunchScreen.storyboard; 311 | sourceTree = ""; 312 | }; 313 | /* End PBXVariantGroup section */ 314 | 315 | /* Begin XCBuildConfiguration section */ 316 | 3C2E434D201E7E6C00E4254A /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ALWAYS_SEARCH_USER_PATHS = NO; 320 | CLANG_ANALYZER_NONNULL = YES; 321 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_COMMA = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INFINITE_RECURSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 339 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 342 | CLANG_WARN_STRICT_PROTOTYPES = YES; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | CODE_SIGN_IDENTITY = ""; 348 | COPY_PHASE_STRIP = NO; 349 | CURRENT_PROJECT_VERSION = 1; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_TESTABILITY = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_DYNAMIC_NO_PIC = NO; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_OPTIMIZATION_LEVEL = 0; 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "DEBUG=1", 359 | "$(inherited)", 360 | ); 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 368 | MTL_ENABLE_DEBUG_INFO = YES; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 372 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 373 | SWIFT_VERSION = 5.0; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | VERSIONING_SYSTEM = "apple-generic"; 376 | VERSION_INFO_PREFIX = ""; 377 | }; 378 | name = Debug; 379 | }; 380 | 3C2E434E201E7E6C00E4254A /* Release */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 391 | CLANG_WARN_BOOL_CONVERSION = YES; 392 | CLANG_WARN_COMMA = YES; 393 | CLANG_WARN_CONSTANT_CONVERSION = YES; 394 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 396 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 397 | CLANG_WARN_EMPTY_BODY = YES; 398 | CLANG_WARN_ENUM_CONVERSION = YES; 399 | CLANG_WARN_INFINITE_RECURSION = YES; 400 | CLANG_WARN_INT_CONVERSION = YES; 401 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 403 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 404 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 405 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 406 | CLANG_WARN_STRICT_PROTOTYPES = YES; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 409 | CLANG_WARN_UNREACHABLE_CODE = YES; 410 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 411 | CODE_SIGN_IDENTITY = ""; 412 | COPY_PHASE_STRIP = NO; 413 | CURRENT_PROJECT_VERSION = 1; 414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 415 | ENABLE_NS_ASSERTIONS = NO; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | GCC_C_LANGUAGE_STANDARD = gnu11; 418 | GCC_NO_COMMON_BLOCKS = YES; 419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 421 | GCC_WARN_UNDECLARED_SELECTOR = YES; 422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 423 | GCC_WARN_UNUSED_FUNCTION = YES; 424 | GCC_WARN_UNUSED_VARIABLE = YES; 425 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 426 | MTL_ENABLE_DEBUG_INFO = NO; 427 | SDKROOT = iphoneos; 428 | SWIFT_COMPILATION_MODE = wholemodule; 429 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 430 | SWIFT_VERSION = 5.0; 431 | TARGETED_DEVICE_FAMILY = "1,2"; 432 | VALIDATE_PRODUCT = YES; 433 | VERSIONING_SYSTEM = "apple-generic"; 434 | VERSION_INFO_PREFIX = ""; 435 | }; 436 | name = Release; 437 | }; 438 | 3C2E4350201E7E6C00E4254A /* Debug */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | APPLICATION_EXTENSION_API_ONLY = YES; 442 | DEFINES_MODULE = YES; 443 | DYLIB_COMPATIBILITY_VERSION = 1; 444 | DYLIB_CURRENT_VERSION = 1; 445 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 446 | INFOPLIST_FILE = Framework/Info.plist; 447 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 448 | LD_RUNPATH_SEARCH_PATHS = ( 449 | "$(inherited)", 450 | "@executable_path/Frameworks", 451 | "@loader_path/Frameworks", 452 | ); 453 | PRODUCT_BUNDLE_IDENTIFIER = net.ianmcdowell.InputAssistant; 454 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 455 | SKIP_INSTALL = YES; 456 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 457 | }; 458 | name = Debug; 459 | }; 460 | 3C2E4351201E7E6C00E4254A /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | APPLICATION_EXTENSION_API_ONLY = YES; 464 | DEFINES_MODULE = YES; 465 | DYLIB_COMPATIBILITY_VERSION = 1; 466 | DYLIB_CURRENT_VERSION = 1; 467 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 468 | INFOPLIST_FILE = Framework/Info.plist; 469 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 470 | LD_RUNPATH_SEARCH_PATHS = ( 471 | "$(inherited)", 472 | "@executable_path/Frameworks", 473 | "@loader_path/Frameworks", 474 | ); 475 | PRODUCT_BUNDLE_IDENTIFIER = net.ianmcdowell.InputAssistant; 476 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 477 | SKIP_INSTALL = YES; 478 | }; 479 | name = Release; 480 | }; 481 | 3C2E436B201E883E00E4254A /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 485 | CODE_SIGN_IDENTITY = "iPhone Developer"; 486 | INFOPLIST_FILE = Sample/Info.plist; 487 | LD_RUNPATH_SEARCH_PATHS = ( 488 | "$(inherited)", 489 | "@executable_path/Frameworks", 490 | ); 491 | PRODUCT_BUNDLE_IDENTIFIER = "net.ianmcdowell.InputAssistant-Sample"; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | }; 494 | name = Debug; 495 | }; 496 | 3C2E436C201E883E00E4254A /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 500 | CODE_SIGN_IDENTITY = "iPhone Developer"; 501 | INFOPLIST_FILE = Sample/Info.plist; 502 | LD_RUNPATH_SEARCH_PATHS = ( 503 | "$(inherited)", 504 | "@executable_path/Frameworks", 505 | ); 506 | PRODUCT_BUNDLE_IDENTIFIER = "net.ianmcdowell.InputAssistant-Sample"; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | }; 509 | name = Release; 510 | }; 511 | 3C2E4380201EFACD00E4254A /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | OTHER_LDFLAGS = "-ObjC"; 515 | PRODUCT_NAME = "$(TARGET_NAME)"; 516 | SKIP_INSTALL = YES; 517 | }; 518 | name = Debug; 519 | }; 520 | 3C2E4381201EFACD00E4254A /* Release */ = { 521 | isa = XCBuildConfiguration; 522 | buildSettings = { 523 | OTHER_LDFLAGS = "-ObjC"; 524 | PRODUCT_NAME = "$(TARGET_NAME)"; 525 | SKIP_INSTALL = YES; 526 | }; 527 | name = Release; 528 | }; 529 | /* End XCBuildConfiguration section */ 530 | 531 | /* Begin XCConfigurationList section */ 532 | 3C2E4341201E7E6C00E4254A /* Build configuration list for PBXProject "InputAssistant" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | 3C2E434D201E7E6C00E4254A /* Debug */, 536 | 3C2E434E201E7E6C00E4254A /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | 3C2E434F201E7E6C00E4254A /* Build configuration list for PBXNativeTarget "InputAssistant" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 3C2E4350201E7E6C00E4254A /* Debug */, 545 | 3C2E4351201E7E6C00E4254A /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | 3C2E436A201E883E00E4254A /* Build configuration list for PBXNativeTarget "InputAssistant Sample" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | 3C2E436B201E883E00E4254A /* Debug */, 554 | 3C2E436C201E883E00E4254A /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | 3C2E437F201EFACD00E4254A /* Build configuration list for PBXNativeTarget "libInputAssistant" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | 3C2E4380201EFACD00E4254A /* Debug */, 563 | 3C2E4381201EFACD00E4254A /* Release */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | /* End XCConfigurationList section */ 569 | }; 570 | rootObject = 3C2E433E201E7E6C00E4254A /* Project object */; 571 | } 572 | -------------------------------------------------------------------------------- /InputAssistant.xcodeproj/xcshareddata/xcschemes/InputAssistant Sample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /InputAssistant.xcodeproj/xcshareddata/xcschemes/InputAssistant.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ian McDowell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Input Assistant 2 | 3 |

4 | TiltedTabView • 5 | TabView • 6 | InputAssistant • 7 | Git 8 |

9 | 10 | -------- 11 | 12 | This library is a view that shows custom auto-complete suggestions for your UITextField / UITextView. 13 | 14 | [![Build Status](http://img.shields.io/travis/IMcD23/InputAssistant.svg)](https://travis-ci.org/IMcD23/InputAssistant) 15 | [![Version](https://img.shields.io/github/release/IMcD23/InputAssistant.svg)](https://github.com/IMcD23/InputAssistant/releases/latest) 16 | ![Package Managers](https://img.shields.io/badge/supports-Carthage-orange.svg) 17 | [![Contact](https://img.shields.io/badge/contact-%40ian__mcdowell-3a8fc1.svg)](https://twitter.com/ian_mcdowell) 18 | 19 | 20 | 21 | # Requirements 22 | 23 | * Xcode 9 or later 24 | * iOS 10.0 or later 25 | 26 | # Usage 27 | 28 | This library provides an `InputAssistantView` class, that is designed to be set as the `inputAccessoryView` of a UITextView or UITextField. 29 | 30 | It provides three areas that you can customize. 31 | - Suggestions - A scrollable set of text suggestions. 32 | - Leading/Trailing actions - tappable buttons on either side of the suggestions. 33 | - Empty text - Optional text that can be displayed when there are no suggestions. 34 | 35 | Use the `InputAssistantViewDataSource` protocol that allows you to do this customization. 36 | 37 | To react to a suggestion being tapped, conform to the `InputAssistantViewDelegate` protocol. 38 | 39 | # Installation 40 | 41 | ## Carthage 42 | To install InputAssistant using [Carthage](https://github.com/Carthage/Carthage), add the following line to your Cartfile: 43 | 44 | ``` 45 | github "IMcD23/InputAssistant" "master" 46 | ``` 47 | 48 | ## Submodule 49 | To install InputAssistant as a submodule into your git repository, run the following command: 50 | 51 | ``` 52 | git submodule add -b master https://github.com/IMcD23/InputAssistant.git Path/To/InputAssistant 53 | git submodule update --init --recursive 54 | ``` 55 | 56 | Then, add the `.xcodeproj` in the root of the repository into your Xcode project, and add it as a build dependency. 57 | 58 | ## ibuild 59 | A Swift static library of this project is also available for the ibuild build system. Learn more about ibuild [here](https://github.com/IMcD23/ibuild) 60 | 61 | # Author 62 | Created by [Ian McDowell](https://ianmcdowell.net) 63 | 64 | # License 65 | All code in this project is available under the license specified in the LICENSE file. However, since this project also bundles code from other projects, you are subject to those projects' licenses as well. 66 | -------------------------------------------------------------------------------- /Resources/Keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-mcdowell/InputAssistant/db3ec1c49a8c3d612899956d124854205bf3369a/Resources/Keyboard.png -------------------------------------------------------------------------------- /Resources/Keyboard_iPad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-mcdowell/InputAssistant/db3ec1c49a8c3d612899956d124854205bf3369a/Resources/Keyboard_iPad.png -------------------------------------------------------------------------------- /Sample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // InputAssistant Sample 4 | // 5 | // Created by Ian McDowell on 1/28/18. 6 | // Copyright © 2018 Ian McDowell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | window?.rootViewController = ViewController() 20 | window?.makeKeyAndVisible() 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { } 26 | func applicationDidEnterBackground(_ application: UIApplication) { } 27 | func applicationWillEnterForeground(_ application: UIApplication) { } 28 | func applicationDidBecomeActive(_ application: UIApplication) { } 29 | func applicationWillTerminate(_ application: UIApplication) { } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Sample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Sample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Sample/Assets.xcassets/Down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "down.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Sample/Assets.xcassets/Down.imageset/down.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ian-mcdowell/InputAssistant/db3ec1c49a8c3d612899956d124854205bf3369a/Sample/Assets.xcassets/Down.imageset/down.pdf -------------------------------------------------------------------------------- /Sample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // InputAssistant Sample 4 | // 5 | // Created by Ian McDowell on 1/28/18. 6 | // Copyright © 2018 Ian McDowell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import InputAssistant 11 | 12 | class ViewController: UIViewController { 13 | 14 | let textView: UITextView 15 | let inputAssistantView: InputAssistantView 16 | let allSuggestions = ["Suggestion", "Test", "Hello", "World", "More", "Suggestions"] 17 | 18 | init() { 19 | textView = UITextView() 20 | inputAssistantView = InputAssistantView() 21 | super.init(nibName: nil, bundle: nil) 22 | 23 | inputAssistantView.delegate = self 24 | inputAssistantView.dataSource = self 25 | inputAssistantView.leadingActions = [] 26 | inputAssistantView.trailingActions = [ 27 | InputAssistantAction(image: #imageLiteral(resourceName: "Down"), target: self, action: #selector(downTapped)) 28 | ] 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | view.backgroundColor = .groupTableViewBackground 37 | 38 | inputAssistantView.attach(to: textView) 39 | 40 | view.addSubview(textView) 41 | textView.translatesAutoresizingMaskIntoConstraints = false 42 | NSLayoutConstraint.activate([ 43 | textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), 44 | textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), 45 | textView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), 46 | textView.bottomAnchor.constraint(lessThanOrEqualTo: view.layoutMarginsGuide.bottomAnchor), 47 | textView.heightAnchor.constraint(equalToConstant: 120) 48 | ]) 49 | } 50 | 51 | @objc private func addTapped() { 52 | print("Add") 53 | } 54 | 55 | @objc private func downTapped() { 56 | textView.resignFirstResponder() 57 | } 58 | } 59 | 60 | extension ViewController: InputAssistantViewDataSource { 61 | 62 | func textForEmptySuggestionsInInputAssistantView() -> String? { 63 | return "No suggestions" 64 | } 65 | 66 | func numberOfSuggestionsInInputAssistantView() -> Int { 67 | return allSuggestions.count 68 | } 69 | 70 | func inputAssistantView(_ inputAssistantView: InputAssistantView, nameForSuggestionAtIndex index: Int) -> String { 71 | return allSuggestions[index] 72 | } 73 | } 74 | 75 | extension ViewController: InputAssistantViewDelegate { 76 | 77 | func inputAssistantView(_ inputAssistantView: InputAssistantView, didSelectSuggestionAtIndex index: Int) { 78 | 79 | self.textView.insertText(allSuggestions[index]) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/InputAssistantCollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputAssistantCollectionView.swift 3 | // InputAssistant 4 | // 5 | // Created by Ian McDowell on 1/28/18. 6 | // Copyright © 2018 Ian McDowell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class InputAssistantCollectionView: UICollectionView { 12 | 13 | /// Reference to the containing input assistant view 14 | weak var inputAssistantView: InputAssistantView? 15 | 16 | /// Width constraint that equals the contentSize.width of the collection view. Low priority. 17 | var widthConstraint: NSLayoutConstraint? 18 | 19 | /// Label to display when there are no suggestions 20 | private let noSuggestionsLabel = UILabel() 21 | 22 | init() { 23 | let layout = UICollectionViewFlowLayout() 24 | layout.estimatedItemSize = CGSize(width: 100, height: 41) 25 | layout.itemSize = UICollectionViewFlowLayout.automaticSize 26 | layout.scrollDirection = .horizontal 27 | layout.minimumInteritemSpacing = 10 28 | layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) 29 | super.init(frame: .zero, collectionViewLayout: layout) 30 | 31 | register(InputAssistantCollectionViewCell.self, forCellWithReuseIdentifier: "Suggestion") 32 | backgroundColor = .clear 33 | showsHorizontalScrollIndicator = false 34 | showsVerticalScrollIndicator = false 35 | delaysContentTouches = false 36 | dataSource = self 37 | 38 | noSuggestionsLabel.textAlignment = .center 39 | noSuggestionsLabel.autoresizingMask = [.flexibleWidth, .flexibleHeight] 40 | noSuggestionsLabel.textColor = UIColor.darkGray 41 | addSubview(noSuggestionsLabel) 42 | 43 | widthConstraint = self.widthAnchor.constraint(equalToConstant: 0) 44 | widthConstraint?.priority = .defaultLow 45 | widthConstraint?.isActive = true 46 | } 47 | 48 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 49 | 50 | override func reloadData() { 51 | super.reloadData() 52 | 53 | // Need to reset scrolling position, 54 | // since the self sizing cells can cause a crash when scrolling back after a reloadData. 55 | contentOffset = .zero 56 | 57 | noSuggestionsLabel.text = self.inputAssistantView?.dataSource?.textForEmptySuggestionsInInputAssistantView() 58 | noSuggestionsLabel.isHidden = self.numberOfItems(inSection: 0) > 0 59 | } 60 | 61 | override var contentSize: CGSize { 62 | didSet { 63 | // If there is no data, make the width the width of the no suggestions label. 64 | // Otherwise, it should be equal to the content size of the collectionView 65 | if !noSuggestionsLabel.isHidden { 66 | let targetLabelSize = noSuggestionsLabel.systemLayoutSizeFitting(CGSize(width: 9999, height: 55)) 67 | widthConstraint?.constant = targetLabelSize.width 68 | } else { 69 | widthConstraint?.constant = contentSize.width 70 | } 71 | } 72 | } 73 | } 74 | 75 | extension InputAssistantCollectionView: UICollectionViewDataSource { 76 | 77 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 78 | return 1 79 | } 80 | 81 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 82 | return inputAssistantView?.dataSource?.numberOfSuggestionsInInputAssistantView() ?? 0 83 | } 84 | 85 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 86 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Suggestion", for: indexPath) as! InputAssistantCollectionViewCell 87 | 88 | guard let inputAssistantView = inputAssistantView, let name = inputAssistantView.dataSource?.inputAssistantView(inputAssistantView, nameForSuggestionAtIndex: indexPath.row) else { 89 | fatalError("No suggestion name found at index.") 90 | } 91 | 92 | cell.label.text = name 93 | cell.keyboardAppearance = inputAssistantView.keyboardAppearance 94 | 95 | return cell 96 | } 97 | } 98 | 99 | private class InputAssistantCollectionViewCell: UICollectionViewCell { 100 | 101 | let label: UILabel 102 | let highlightedBackgroundColor = UIColor(red: 235/255, green: 237/255, blue: 239/255, alpha: 1) 103 | let regularBackgroundColor = UIColor(red: 174/255, green: 180/255, blue: 186/255, alpha: 1) 104 | let darkBackgroundColor = UIColor(white: 200/255, alpha: 0.4) 105 | 106 | var keyboardAppearance: UIKeyboardAppearance = .default { 107 | didSet { updateSelectionState() } 108 | } 109 | 110 | private var keyboardAppearanceBackgroundColor: UIColor { 111 | switch keyboardAppearance { 112 | case .dark: return self.darkBackgroundColor 113 | default: return self.regularBackgroundColor 114 | } 115 | } 116 | 117 | override init(frame: CGRect) { 118 | label = UILabel() 119 | 120 | super.init(frame: frame) 121 | 122 | label.textAlignment = .center 123 | 124 | self.contentView.addSubview(label) 125 | label.translatesAutoresizingMaskIntoConstraints = false 126 | NSLayoutConstraint.activate([ 127 | label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10), 128 | label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10), 129 | label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), 130 | label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), 131 | label.widthAnchor.constraint(greaterThanOrEqualToConstant: 50) 132 | ]) 133 | 134 | self.layer.cornerRadius = 4 135 | self.layer.masksToBounds = true 136 | updateSelectionState() 137 | } 138 | 139 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 140 | 141 | override var isHighlighted: Bool { 142 | didSet { updateSelectionState() } 143 | } 144 | override var isSelected: Bool { 145 | didSet { updateSelectionState() } 146 | } 147 | 148 | private func updateSelectionState() { 149 | let isHighlighted = self.isHighlighted || self.isSelected 150 | self.backgroundColor = isHighlighted ? self.highlightedBackgroundColor : self.keyboardAppearanceBackgroundColor 151 | self.label.textColor = isHighlighted ? .black : .white 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Sources/InputAssistantView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputAssistantView.swift 3 | // InputAssistant 4 | // 5 | // Created by Ian McDowell on 1/28/18. 6 | // Copyright © 2018 Ian McDowell. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A button to be displayed in on the leading or trailing side of an input assistant. 12 | public struct InputAssistantAction { 13 | 14 | /// Image to display to the user. Will be resized to fit the height of the input assistant. 15 | public let image: UIImage 16 | 17 | public weak var target: AnyObject? 18 | public let action: Selector? 19 | 20 | public init(image: UIImage, target: AnyObject? = nil, action: Selector? = nil) { 21 | self.image = image; self.target = target; self.action = action 22 | } 23 | } 24 | 25 | public protocol InputAssistantViewDataSource: class { 26 | 27 | /// Text to display when there are no suggestions. 28 | func textForEmptySuggestionsInInputAssistantView() -> String? 29 | 30 | /// Number of suggestions to display 31 | func numberOfSuggestionsInInputAssistantView() -> Int 32 | 33 | /// Return information about the suggestion at the given index 34 | func inputAssistantView(_ inputAssistantView: InputAssistantView, nameForSuggestionAtIndex index: Int) -> String 35 | } 36 | 37 | /// Delegate to receive notifications about user actions in the input assistant view. 38 | public protocol InputAssistantViewDelegate: class { 39 | 40 | /// When the user taps on a suggestion 41 | func inputAssistantView(_ inputAssistantView: InputAssistantView, didSelectSuggestionAtIndex index: Int) 42 | } 43 | 44 | /// UIInputView that displays custom suggestions, as well as leading and trailing actions. 45 | open class InputAssistantView: UIInputView { 46 | 47 | /// Actions to display on the leading side of the suggestions. 48 | public var leadingActions: [InputAssistantAction] = [] { 49 | didSet { self.updateActions(leadingActions, leadingStackView) } 50 | } 51 | 52 | /// Actions to display on the trailing side of the suggestions 53 | public var trailingActions: [InputAssistantAction] = [] { 54 | didSet { self.updateActions(trailingActions, trailingStackView) } 55 | } 56 | 57 | /// Set this to receive notifications when things happen in the assistant view. 58 | public weak var delegate: InputAssistantViewDelegate? 59 | 60 | /// Set this to provide data to the input assistant view 61 | public weak var dataSource: InputAssistantViewDataSource? { 62 | didSet { suggestionsCollectionView.reloadData() } 63 | } 64 | 65 | /// Stack view on the leading side of the collection view. Contains actions. 66 | private let leadingStackView: UIStackView 67 | 68 | /// Stack view on the trailing side of the collection view. Contains actions. 69 | private let trailingStackView: UIStackView 70 | 71 | /// Collection view, with a horizontally scrolling set of suggestions. 72 | private let suggestionsCollectionView: InputAssistantCollectionView 73 | 74 | public init() { 75 | self.leadingStackView = UIStackView() 76 | self.trailingStackView = UIStackView() 77 | 78 | self.suggestionsCollectionView = InputAssistantCollectionView() 79 | 80 | super.init(frame: .init(origin: .zero, size: .init(width: 0, height: 55)), inputViewStyle: .default) 81 | 82 | self.suggestionsCollectionView.inputAssistantView = self 83 | self.suggestionsCollectionView.delegate = self 84 | 85 | for stackView in [leadingStackView, trailingStackView] { 86 | stackView.spacing = 10 87 | stackView.alignment = .center 88 | stackView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) 89 | updateActions([], stackView) 90 | } 91 | 92 | // suggestions stretch to fill 93 | suggestionsCollectionView.setContentHuggingPriority(.defaultLow, for: .horizontal) 94 | 95 | // The stack views are embedded into a container, which lays them out horizontally 96 | let containerStackView = UIStackView(arrangedSubviews: [leadingStackView, suggestionsCollectionView, trailingStackView]) 97 | containerStackView.alignment = .fill 98 | containerStackView.axis = .horizontal 99 | containerStackView.distribution = .equalCentering 100 | 101 | // Stretch to fill bounds 102 | containerStackView.frame = self.bounds 103 | self.addSubview(containerStackView) 104 | containerStackView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 105 | } 106 | 107 | public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 108 | 109 | public func reloadData() { 110 | suggestionsCollectionView.reloadData() 111 | } 112 | 113 | /// The keyboard appearance of the attached text input 114 | internal var keyboardAppearance: UIKeyboardAppearance = .default { 115 | didSet { 116 | switch keyboardAppearance { 117 | case .dark: self.tintColor = .white 118 | default: self.tintColor = .black 119 | } 120 | } 121 | } 122 | private var keyboardAppearanceObserver: NSKeyValueObservation? 123 | 124 | /// Attach the inputAssistant to the given UITextView. 125 | public func attach(to textInput: UITextView) { 126 | self.keyboardAppearance = textInput.keyboardAppearance 127 | 128 | // Hide default undo/redo/etc buttons 129 | textInput.inputAssistantItem.leadingBarButtonGroups = [] 130 | textInput.inputAssistantItem.trailingBarButtonGroups = [] 131 | 132 | // Disable built-in autocomplete 133 | textInput.autocorrectionType = .no 134 | 135 | // Add the input assistant view as an accessory view 136 | textInput.inputAccessoryView = self 137 | 138 | keyboardAppearanceObserver = textInput.observe(\UITextView.keyboardAppearance) { [weak self] textInput, _ in 139 | self?.keyboardAppearance = textInput.keyboardAppearance 140 | } 141 | } 142 | /// Attach the inputAssistant to the given UITextView. 143 | public func attach(to textInput: UITextField) { 144 | self.keyboardAppearance = textInput.keyboardAppearance 145 | 146 | // Hide default undo/redo/etc buttons 147 | textInput.inputAssistantItem.leadingBarButtonGroups = [] 148 | textInput.inputAssistantItem.trailingBarButtonGroups = [] 149 | 150 | // Disable built-in autocomplete 151 | textInput.autocorrectionType = .no 152 | 153 | // Add the input assistant view as an accessory view 154 | textInput.inputAccessoryView = self 155 | 156 | keyboardAppearanceObserver = textInput.observe(\UITextField.keyboardAppearance) { [weak self] textInput, _ in 157 | self?.keyboardAppearance = textInput.keyboardAppearance 158 | } 159 | } 160 | 161 | open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 162 | super.traitCollectionDidChange(previousTraitCollection) 163 | updateActions(leadingActions, leadingStackView) 164 | updateActions(trailingActions, trailingStackView) 165 | } 166 | 167 | /// Remove existing actions, and add new ones to the given leading/trailing stack view. 168 | private func updateActions(_ actions: [InputAssistantAction], _ stackView: UIStackView) { 169 | for view in stackView.arrangedSubviews { 170 | view.removeFromSuperview() 171 | } 172 | if actions.isEmpty { 173 | let emptyView = UIView() 174 | emptyView.widthAnchor.constraint(equalToConstant: 0).isActive = true 175 | stackView.addArrangedSubview(emptyView) 176 | } else { 177 | let itemWidth: CGFloat = self.traitCollection.horizontalSizeClass == .regular ? 60 : 40 178 | for action in actions { 179 | let button = UIButton.init(type: .system) 180 | button.setImage(action.image.scaled(toSize: CGSize(width: 25, height: 25)), for: .normal) 181 | if let target = action.target, let action = action.action { 182 | button.addTarget(target, action: action, for: .touchUpInside) 183 | } 184 | 185 | // If possible, the button should be at least 40px wide for a good sized tap target 186 | 187 | let widthConstraint = button.widthAnchor.constraint(equalToConstant: itemWidth) 188 | widthConstraint.priority = .defaultHigh 189 | widthConstraint.isActive = true 190 | 191 | stackView.addArrangedSubview(button) 192 | } 193 | } 194 | } 195 | } 196 | 197 | extension UIImage { 198 | 199 | /// Scales the image to the given CGSize 200 | func scaled(toSize size: CGSize) -> UIImage { 201 | if self.size == size { 202 | return self 203 | } 204 | 205 | let newImage = UIGraphicsImageRenderer(size: size).image { context in 206 | self.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) 207 | } 208 | return newImage.withRenderingMode(self.renderingMode) 209 | } 210 | } 211 | 212 | extension InputAssistantView: UICollectionViewDelegate { 213 | 214 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 215 | UIDevice.current.playInputClick() 216 | collectionView.deselectItem(at: indexPath, animated: true) 217 | 218 | self.delegate?.inputAssistantView(self, didSelectSuggestionAtIndex: indexPath.row) 219 | } 220 | } 221 | 222 | extension InputAssistantView: UIInputViewAudioFeedback { 223 | 224 | public var enableInputClicksWhenVisible: Bool { return true } 225 | } 226 | 227 | -------------------------------------------------------------------------------- /build.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | InputAssistant 7 | build 8 | 9 | buildSystem 10 | xcode 11 | buildArgs 12 | 13 | -project 14 | InputAssistant.xcodeproj 15 | -target 16 | libInputAssistant 17 | 18 | outputs 19 | 20 | libInputAssistant.a 21 | 22 | 23 | dependencies 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------