├── Categories ├── IQNSArray+Sort.swift ├── IQUIScrollView+Additions.swift ├── IQUITextFieldView+Additions.swift ├── IQUIView+Hierarchy.swift └── IQUIViewController+Additions.swift ├── Constants ├── IQKeyboardManagerConstants.swift └── IQKeyboardManagerConstantsInternal.swift ├── IQKeyboardManager.swift ├── IQKeyboardManagerSwift.h ├── IQKeyboardReturnKeyHandler.swift ├── IQTextView └── IQTextView.swift ├── IQToolbar ├── IQBarButtonItem.swift ├── IQInvocation.swift ├── IQPreviousNextView.swift ├── IQTitleBarButtonItem.swift ├── IQToolbar.swift └── IQUIView+IQKeyboardToolbar.swift ├── README.md └── Resources └── IQKeyboardManager.bundle ├── IQButtonBarArrowDown@2x.png ├── IQButtonBarArrowDown@3x.png ├── IQButtonBarArrowLeft@2x.png ├── IQButtonBarArrowLeft@3x.png ├── IQButtonBarArrowRight@2x.png ├── IQButtonBarArrowRight@3x.png ├── IQButtonBarArrowUp@2x.png └── IQButtonBarArrowUp@3x.png /Categories/IQNSArray+Sort.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQNSArray+Sort.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | /** 28 | UIView.subviews sorting category. 29 | */ 30 | internal extension Array where Element: UIView { 31 | 32 | ///-------------- 33 | /// MARK: Sorting 34 | ///-------------- 35 | 36 | /** 37 | Returns the array by sorting the UIView's by their tag property. 38 | */ 39 | func sortedArrayByTag() -> [Element] { 40 | 41 | return sorted(by: { (obj1: Element, obj2: Element) -> Bool in 42 | 43 | return (obj1.tag < obj2.tag) 44 | }) 45 | } 46 | 47 | /** 48 | Returns the array by sorting the UIView's by their tag property. 49 | */ 50 | func sortedArrayByPosition() -> [Element] { 51 | 52 | return sorted(by: { (obj1: Element, obj2: Element) -> Bool in 53 | if obj1.frame.minY != obj2.frame.minY { 54 | return obj1.frame.minY < obj2.frame.minY 55 | } else { 56 | return obj1.frame.minX < obj2.frame.minX 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Categories/IQUIScrollView+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIScrollView+Additions.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | private var kIQShouldIgnoreScrollingAdjustment = "kIQShouldIgnoreScrollingAdjustment" 28 | private var kIQShouldRestoreScrollViewContentOffset = "kIQShouldRestoreScrollViewContentOffset" 29 | 30 | @objc public extension UIScrollView { 31 | 32 | /** 33 | If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textfield position. Default is NO. 34 | */ 35 | @objc var shouldIgnoreScrollingAdjustment: Bool { 36 | get { 37 | 38 | if let aValue = objc_getAssociatedObject(self, &kIQShouldIgnoreScrollingAdjustment) as? Bool { 39 | return aValue 40 | } else { 41 | return false 42 | } 43 | } 44 | set(newValue) { 45 | objc_setAssociatedObject(self, &kIQShouldIgnoreScrollingAdjustment, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 46 | } 47 | } 48 | 49 | /** 50 | To set customized distance from keyboard for textField/textView. Can't be less than zero 51 | */ 52 | @objc var shouldRestoreScrollViewContentOffset: Bool { 53 | get { 54 | 55 | if let aValue = objc_getAssociatedObject(self, &kIQShouldRestoreScrollViewContentOffset) as? Bool { 56 | return aValue 57 | } else { 58 | return false 59 | } 60 | } 61 | set(newValue) { 62 | objc_setAssociatedObject(self, &kIQShouldRestoreScrollViewContentOffset, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 63 | } 64 | } 65 | } 66 | 67 | internal extension UITableView { 68 | 69 | func previousIndexPath(of indexPath: IndexPath) -> IndexPath? { 70 | var previousRow = indexPath.row - 1 71 | var previousSection = indexPath.section 72 | 73 | //Fixing indexPath 74 | if previousRow < 0 { 75 | previousSection -= 1 76 | 77 | if previousSection >= 0 { 78 | previousRow = self.numberOfRows(inSection: previousSection) - 1 79 | } 80 | } 81 | 82 | if previousRow >= 0 && previousSection >= 0 { 83 | return IndexPath(row: previousRow, section: previousSection) 84 | } else { 85 | return nil 86 | } 87 | } 88 | } 89 | 90 | internal extension UICollectionView { 91 | 92 | func previousIndexPath(of indexPath: IndexPath) -> IndexPath? { 93 | var previousRow = indexPath.row - 1 94 | var previousSection = indexPath.section 95 | 96 | //Fixing indexPath 97 | if previousRow < 0 { 98 | previousSection -= 1 99 | 100 | if previousSection >= 0 { 101 | previousRow = self.numberOfItems(inSection: previousSection) - 1 102 | } 103 | } 104 | 105 | if previousRow >= 0 && previousSection >= 0 { 106 | return IndexPath(item: previousRow, section: previousSection) 107 | } else { 108 | return nil 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Categories/IQUITextFieldView+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITextFieldView+Additions.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | /** 28 | Uses default keyboard distance for textField. 29 | */ 30 | public let kIQUseDefaultKeyboardDistance = CGFloat.greatestFiniteMagnitude 31 | 32 | private var kIQKeyboardDistanceFromTextField = "kIQKeyboardDistanceFromTextField" 33 | //private var kIQKeyboardEnableMode = "kIQKeyboardEnableMode" 34 | private var kIQShouldResignOnTouchOutsideMode = "kIQShouldResignOnTouchOutsideMode" 35 | private var kIQIgnoreSwitchingByNextPrevious = "kIQIgnoreSwitchingByNextPrevious" 36 | 37 | /** 38 | UIView category for managing UITextField/UITextView 39 | */ 40 | @objc public extension UIView { 41 | 42 | /** 43 | To set customized distance from keyboard for textField/textView. Can't be less than zero 44 | */ 45 | @objc var keyboardDistanceFromTextField: CGFloat { 46 | get { 47 | 48 | if let aValue = objc_getAssociatedObject(self, &kIQKeyboardDistanceFromTextField) as? CGFloat { 49 | return aValue 50 | } else { 51 | return kIQUseDefaultKeyboardDistance 52 | } 53 | } 54 | set(newValue) { 55 | objc_setAssociatedObject(self, &kIQKeyboardDistanceFromTextField, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 56 | } 57 | } 58 | 59 | /** 60 | If shouldIgnoreSwitchingByNextPrevious is true then library will ignore this textField/textView while moving to other textField/textView using keyboard toolbar next previous buttons. Default is false 61 | */ 62 | @objc var ignoreSwitchingByNextPrevious: Bool { 63 | get { 64 | 65 | if let aValue = objc_getAssociatedObject(self, &kIQIgnoreSwitchingByNextPrevious) as? Bool { 66 | return aValue 67 | } else { 68 | return false 69 | } 70 | } 71 | set(newValue) { 72 | objc_setAssociatedObject(self, &kIQIgnoreSwitchingByNextPrevious, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 73 | } 74 | } 75 | 76 | // /** 77 | // Override Enable/disable managing distance between keyboard and textField behaviour for this particular textField. 78 | // */ 79 | // @objc public var enableMode: IQEnableMode { 80 | // get { 81 | // 82 | // if let savedMode = objc_getAssociatedObject(self, &kIQKeyboardEnableMode) as? IQEnableMode { 83 | // return savedMode 84 | // } else { 85 | // return .default 86 | // } 87 | // } 88 | // set(newValue) { 89 | // objc_setAssociatedObject(self, &kIQKeyboardEnableMode, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 90 | // } 91 | // } 92 | 93 | /** 94 | Override resigns Keyboard on touching outside of UITextField/View behaviour for this particular textField. 95 | */ 96 | @objc var shouldResignOnTouchOutsideMode: IQEnableMode { 97 | get { 98 | 99 | if let savedMode = objc_getAssociatedObject(self, &kIQShouldResignOnTouchOutsideMode) as? IQEnableMode { 100 | return savedMode 101 | } else { 102 | return .default 103 | } 104 | } 105 | set(newValue) { 106 | objc_setAssociatedObject(self, &kIQShouldResignOnTouchOutsideMode, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Categories/IQUIView+Hierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIView+Hierarchy.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | /** 27 | UIView hierarchy category. 28 | */ 29 | @objc public extension UIView { 30 | 31 | ///---------------------- 32 | /// MARK: viewControllers 33 | ///---------------------- 34 | 35 | /** 36 | Returns the UIViewController object that manages the receiver. 37 | */ 38 | @objc func viewContainingController() -> UIViewController? { 39 | 40 | var nextResponder: UIResponder? = self 41 | 42 | repeat { 43 | nextResponder = nextResponder?.next 44 | 45 | if let viewController = nextResponder as? UIViewController { 46 | return viewController 47 | } 48 | 49 | } while nextResponder != nil 50 | 51 | return nil 52 | } 53 | 54 | /** 55 | Returns the topMost UIViewController object in hierarchy. 56 | */ 57 | @objc func topMostController() -> UIViewController? { 58 | 59 | var controllersHierarchy = [UIViewController]() 60 | 61 | if var topController = window?.rootViewController { 62 | controllersHierarchy.append(topController) 63 | 64 | while let presented = topController.presentedViewController { 65 | 66 | topController = presented 67 | 68 | controllersHierarchy.append(presented) 69 | } 70 | 71 | var matchController: UIResponder? = viewContainingController() 72 | 73 | while let mController = matchController as? UIViewController, controllersHierarchy.contains(mController) == false { 74 | 75 | repeat { 76 | matchController = matchController?.next 77 | 78 | } while matchController != nil && matchController is UIViewController == false 79 | } 80 | 81 | return matchController as? UIViewController 82 | 83 | } else { 84 | return viewContainingController() 85 | } 86 | } 87 | 88 | /** 89 | Returns the UIViewController object that is actually the parent of this object. Most of the time it's the viewController object which actually contains it, but result may be different if it's viewController is added as childViewController of another viewController. 90 | */ 91 | @objc func parentContainerViewController() -> UIViewController? { 92 | 93 | var matchController = viewContainingController() 94 | var parentContainerViewController: UIViewController? 95 | 96 | if var navController = matchController?.navigationController { 97 | 98 | while let parentNav = navController.navigationController { 99 | navController = parentNav 100 | } 101 | 102 | var parentController: UIViewController = navController 103 | 104 | while let parent = parentController.parent, 105 | (parent.isKind(of: UINavigationController.self) == false && 106 | parent.isKind(of: UITabBarController.self) == false && 107 | parent.isKind(of: UISplitViewController.self) == false) { 108 | 109 | parentController = parent 110 | } 111 | 112 | if navController == parentController { 113 | parentContainerViewController = navController.topViewController 114 | } else { 115 | parentContainerViewController = parentController 116 | } 117 | } else if let tabController = matchController?.tabBarController { 118 | 119 | if let navController = tabController.selectedViewController as? UINavigationController { 120 | parentContainerViewController = navController.topViewController 121 | } else { 122 | parentContainerViewController = tabController.selectedViewController 123 | } 124 | } else { 125 | while let parentController = matchController?.parent, 126 | (parentController.isKind(of: UINavigationController.self) == false && 127 | parentController.isKind(of: UITabBarController.self) == false && 128 | parentController.isKind(of: UISplitViewController.self) == false) { 129 | 130 | matchController = parentController 131 | } 132 | 133 | parentContainerViewController = matchController 134 | } 135 | 136 | let finalController = parentContainerViewController?.parentIQContainerViewController() ?? parentContainerViewController 137 | 138 | return finalController 139 | 140 | } 141 | 142 | ///----------------------------------- 143 | /// MARK: Superviews/Subviews/Siglings 144 | ///----------------------------------- 145 | 146 | /** 147 | Returns the superView of provided class type. 148 | 149 | 150 | @param classType class type of the object which is to be search in above hierarchy and return 151 | 152 | @param belowView view object in upper hierarchy where method should stop searching and return nil 153 | */ 154 | @objc func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? { 155 | 156 | var superView = superview 157 | 158 | while let unwrappedSuperView = superView { 159 | 160 | if unwrappedSuperView.isKind(of: classType) { 161 | 162 | //If it's UIScrollView, then validating for special cases 163 | if unwrappedSuperView.isKind(of: UIScrollView.self) { 164 | 165 | let classNameString = NSStringFromClass(type(of: unwrappedSuperView.self)) 166 | 167 | // If it's not UITableViewWrapperView class, this is internal class which is actually manage in UITableview. The speciality of this class is that it's superview is UITableView. 168 | // If it's not UITableViewCellScrollView class, this is internal class which is actually manage in UITableviewCell. The speciality of this class is that it's superview is UITableViewCell. 169 | //If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes 170 | if unwrappedSuperView.superview?.isKind(of: UITableView.self) == false && 171 | unwrappedSuperView.superview?.isKind(of: UITableViewCell.self) == false && 172 | classNameString.hasPrefix("_") == false { 173 | return superView 174 | } 175 | } else { 176 | return superView 177 | } 178 | } else if unwrappedSuperView == belowView { 179 | return nil 180 | } 181 | 182 | superView = unwrappedSuperView.superview 183 | } 184 | 185 | return nil 186 | } 187 | 188 | /** 189 | Returns all siblings of the receiver which canBecomeFirstResponder. 190 | */ 191 | internal func responderSiblings() -> [UIView] { 192 | 193 | //Array of (UITextField/UITextView's). 194 | var tempTextFields = [UIView]() 195 | 196 | // Getting all siblings 197 | if let siblings = superview?.subviews { 198 | 199 | for textField in siblings { 200 | 201 | if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true { 202 | tempTextFields.append(textField) 203 | } 204 | } 205 | } 206 | 207 | return tempTextFields 208 | } 209 | 210 | /** 211 | Returns all deep subViews of the receiver which canBecomeFirstResponder. 212 | */ 213 | internal func deepResponderViews() -> [UIView] { 214 | 215 | //Array of (UITextField/UITextView's). 216 | var textfields = [UIView]() 217 | 218 | for textField in subviews { 219 | 220 | if (textField == self || textField.ignoreSwitchingByNextPrevious == false) && textField.IQcanBecomeFirstResponder() == true { 221 | textfields.append(textField) 222 | } 223 | 224 | //Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458) 225 | //Uncommented else (Bug ID: #625) 226 | if textField.subviews.count != 0 && isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 { 227 | for deepView in textField.deepResponderViews() { 228 | textfields.append(deepView) 229 | } 230 | } 231 | } 232 | 233 | //subviews are returning in opposite order. Sorting according the frames 'y'. 234 | return textfields.sorted(by: { (view1: UIView, view2: UIView) -> Bool in 235 | 236 | let frame1 = view1.convert(view1.bounds, to: self) 237 | let frame2 = view2.convert(view2.bounds, to: self) 238 | 239 | if frame1.minY != frame2.minY { 240 | return frame1.minY < frame2.minY 241 | } else { 242 | return frame1.minX < frame2.minX 243 | } 244 | }) 245 | } 246 | 247 | private func IQcanBecomeFirstResponder() -> Bool { 248 | 249 | var IQcanBecomeFirstResponder = false 250 | 251 | // Setting toolbar to keyboard. 252 | if let textField = self as? UITextField { 253 | IQcanBecomeFirstResponder = textField.isEnabled 254 | } else if let textView = self as? UITextView { 255 | IQcanBecomeFirstResponder = textView.isEditable 256 | } 257 | 258 | if IQcanBecomeFirstResponder == true { 259 | IQcanBecomeFirstResponder = isUserInteractionEnabled == true && isHidden == false && alpha != 0.0 && isAlertViewTextField() == false && textFieldSearchBar() == nil 260 | } 261 | 262 | return IQcanBecomeFirstResponder 263 | } 264 | 265 | ///------------------------- 266 | /// MARK: Special TextFields 267 | ///------------------------- 268 | 269 | /** 270 | Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil. 271 | */ 272 | internal func textFieldSearchBar() -> UISearchBar? { 273 | 274 | var responder: UIResponder? = self.next 275 | 276 | while let bar = responder { 277 | 278 | if let searchBar = bar as? UISearchBar { 279 | return searchBar 280 | } else if bar is UIViewController { 281 | break 282 | } 283 | 284 | responder = bar.next 285 | } 286 | 287 | return nil 288 | } 289 | 290 | /** 291 | Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO. 292 | */ 293 | internal func isAlertViewTextField() -> Bool { 294 | 295 | var alertViewController: UIResponder? = viewContainingController() 296 | 297 | var isAlertViewTextField = false 298 | 299 | while let controller = alertViewController, isAlertViewTextField == false { 300 | 301 | if controller.isKind(of: UIAlertController.self) { 302 | isAlertViewTextField = true 303 | break 304 | } 305 | 306 | alertViewController = controller.next 307 | } 308 | 309 | return isAlertViewTextField 310 | } 311 | 312 | private func depth() -> Int { 313 | var depth: Int = 0 314 | 315 | if let superView = superview { 316 | depth = superView.depth()+1 317 | } 318 | 319 | return depth 320 | } 321 | 322 | } 323 | 324 | @objc public extension UIViewController { 325 | 326 | func parentIQContainerViewController() -> UIViewController? { 327 | return self 328 | } 329 | } 330 | 331 | extension NSObject { 332 | 333 | internal func _IQDescription() -> String { 334 | return "<\(self) \(Unmanaged.passUnretained(self).toOpaque())>" 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /Categories/IQUIViewController+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIViewController+Additions.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | private var kIQLayoutGuideConstraint = "kIQLayoutGuideConstraint" 27 | 28 | @objc public extension UIViewController { 29 | 30 | /** 31 | To set customized distance from keyboard for textField/textView. Can't be less than zero 32 | 33 | @deprecated Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview 34 | */ 35 | @available(*, deprecated, message: "Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview.") 36 | @IBOutlet @objc var IQLayoutGuideConstraint: NSLayoutConstraint? { 37 | get { 38 | 39 | return objc_getAssociatedObject(self, &kIQLayoutGuideConstraint) as? NSLayoutConstraint 40 | } 41 | 42 | set(newValue) { 43 | objc_setAssociatedObject(self, &kIQLayoutGuideConstraint, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Constants/IQKeyboardManagerConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQKeyboardManagerConstants.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | 26 | ///----------------------------------- 27 | /// MARK: IQAutoToolbarManageBehaviour 28 | ///----------------------------------- 29 | 30 | /** 31 | `IQAutoToolbarBySubviews` 32 | Creates Toolbar according to subview's hirarchy of Textfield's in view. 33 | 34 | `IQAutoToolbarByTag` 35 | Creates Toolbar according to tag property of TextField's. 36 | 37 | `IQAutoToolbarByPosition` 38 | Creates Toolbar according to the y,x position of textField in it's superview coordinate. 39 | */ 40 | @objc public enum IQAutoToolbarManageBehaviour: Int { 41 | case bySubviews 42 | case byTag 43 | case byPosition 44 | } 45 | 46 | /** 47 | `IQPreviousNextDisplayModeDefault` 48 | Show NextPrevious when there are more than 1 textField otherwise hide. 49 | 50 | `IQPreviousNextDisplayModeAlwaysHide` 51 | Do not show NextPrevious buttons in any case. 52 | 53 | `IQPreviousNextDisplayModeAlwaysShow` 54 | Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled. 55 | */ 56 | @objc public enum IQPreviousNextDisplayMode: Int { 57 | case `default` 58 | case alwaysHide 59 | case alwaysShow 60 | } 61 | 62 | /** 63 | `IQEnableModeDefault` 64 | Pick default settings. 65 | 66 | `IQEnableModeEnabled` 67 | setting is enabled. 68 | 69 | `IQEnableModeDisabled` 70 | setting is disabled. 71 | */ 72 | @objc public enum IQEnableMode: Int { 73 | case `default` 74 | case enabled 75 | case disabled 76 | } 77 | 78 | /* 79 | /---------------------------------------------------------------------------------------------------\ 80 | \---------------------------------------------------------------------------------------------------/ 81 | | iOS Notification Mechanism | 82 | /---------------------------------------------------------------------------------------------------\ 83 | \---------------------------------------------------------------------------------------------------/ 84 | 85 | ------------------------------------------------------------ 86 | When UITextField become first responder 87 | ------------------------------------------------------------ 88 | - UITextFieldTextDidBeginEditingNotification (UITextField) 89 | - UIKeyboardWillShowNotification 90 | - UIKeyboardDidShowNotification 91 | 92 | ------------------------------------------------------------ 93 | When UITextView become first responder 94 | ------------------------------------------------------------ 95 | - UIKeyboardWillShowNotification 96 | - UITextViewTextDidBeginEditingNotification (UITextView) 97 | - UIKeyboardDidShowNotification 98 | 99 | ------------------------------------------------------------ 100 | When switching focus from UITextField to another UITextField 101 | ------------------------------------------------------------ 102 | - UITextFieldTextDidEndEditingNotification (UITextField1) 103 | - UITextFieldTextDidBeginEditingNotification (UITextField2) 104 | - UIKeyboardWillShowNotification 105 | - UIKeyboardDidShowNotification 106 | 107 | ------------------------------------------------------------ 108 | When switching focus from UITextView to another UITextView 109 | ------------------------------------------------------------ 110 | - UITextViewTextDidEndEditingNotification: (UITextView1) 111 | - UIKeyboardWillShowNotification 112 | - UITextViewTextDidBeginEditingNotification: (UITextView2) 113 | - UIKeyboardDidShowNotification 114 | 115 | ------------------------------------------------------------ 116 | When switching focus from UITextField to UITextView 117 | ------------------------------------------------------------ 118 | - UITextFieldTextDidEndEditingNotification (UITextField) 119 | - UIKeyboardWillShowNotification 120 | - UITextViewTextDidBeginEditingNotification (UITextView) 121 | - UIKeyboardDidShowNotification 122 | 123 | ------------------------------------------------------------ 124 | When switching focus from UITextView to UITextField 125 | ------------------------------------------------------------ 126 | - UITextViewTextDidEndEditingNotification (UITextView) 127 | - UITextFieldTextDidBeginEditingNotification (UITextField) 128 | - UIKeyboardWillShowNotification 129 | - UIKeyboardDidShowNotification 130 | 131 | ------------------------------------------------------------ 132 | When opening/closing UIKeyboard Predictive bar 133 | ------------------------------------------------------------ 134 | - UIKeyboardWillShowNotification 135 | - UIKeyboardDidShowNotification 136 | 137 | ------------------------------------------------------------ 138 | On orientation change 139 | ------------------------------------------------------------ 140 | - UIApplicationWillChangeStatusBarOrientationNotification 141 | - UIKeyboardWillHideNotification 142 | - UIKeyboardDidHideNotification 143 | - UIApplicationDidChangeStatusBarOrientationNotification 144 | - UIKeyboardWillShowNotification 145 | - UIKeyboardDidShowNotification 146 | - UIKeyboardWillShowNotification 147 | - UIKeyboardDidShowNotification 148 | 149 | */ 150 | -------------------------------------------------------------------------------- /Constants/IQKeyboardManagerConstantsInternal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQKeyboardManagerConstantsInternal.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | -------------------------------------------------------------------------------- /IQKeyboardManagerSwift.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQKeyboardManagerSwift.h 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | //! Project version number for IQKeyboardManagerSwift. 27 | FOUNDATION_EXPORT double IQKeyboardManagerSwiftVersionNumber; 28 | 29 | //! Project version string for IQKeyboardManagerSwift. 30 | FOUNDATION_EXPORT const unsigned char IQKeyboardManagerSwiftVersionString[]; 31 | 32 | // In this header, you should import all the public headers of your framework using statements like #import 33 | 34 | 35 | -------------------------------------------------------------------------------- /IQKeyboardReturnKeyHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQKeyboardReturnKeyHandler.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | private class IQTextFieldViewInfoModal: NSObject { 28 | 29 | fileprivate weak var textFieldDelegate: UITextFieldDelegate? 30 | fileprivate weak var textViewDelegate: UITextViewDelegate? 31 | fileprivate weak var textFieldView: UIView? 32 | fileprivate var originalReturnKeyType = UIReturnKeyType.default 33 | 34 | init(textFieldView: UIView?, textFieldDelegate: UITextFieldDelegate?, textViewDelegate: UITextViewDelegate?, originalReturnKeyType: UIReturnKeyType = .default) { 35 | self.textFieldView = textFieldView 36 | self.textFieldDelegate = textFieldDelegate 37 | self.textViewDelegate = textViewDelegate 38 | self.originalReturnKeyType = originalReturnKeyType 39 | } 40 | } 41 | 42 | /** 43 | Manages the return key to work like next/done in a view hierarchy. 44 | */ 45 | public class IQKeyboardReturnKeyHandler: NSObject, UITextFieldDelegate, UITextViewDelegate { 46 | 47 | ///--------------- 48 | /// MARK: Settings 49 | ///--------------- 50 | 51 | /** 52 | Delegate of textField/textView. 53 | */ 54 | @objc public weak var delegate: (UITextFieldDelegate & UITextViewDelegate)? 55 | 56 | /** 57 | Set the last textfield return key type. Default is UIReturnKeyDefault. 58 | */ 59 | @objc public var lastTextFieldReturnKeyType: UIReturnKeyType = UIReturnKeyType.default { 60 | 61 | didSet { 62 | 63 | for modal in textFieldInfoCache { 64 | 65 | if let view = modal.textFieldView { 66 | updateReturnKeyTypeOnTextField(view) 67 | } 68 | } 69 | } 70 | } 71 | 72 | ///-------------------------------------- 73 | /// MARK: Initialization/Deinitialization 74 | ///-------------------------------------- 75 | 76 | @objc public override init() { 77 | super.init() 78 | } 79 | 80 | /** 81 | Add all the textFields available in UIViewController's view. 82 | */ 83 | @objc public init(controller: UIViewController) { 84 | super.init() 85 | 86 | addResponderFromView(controller.view) 87 | } 88 | 89 | deinit { 90 | 91 | for modal in textFieldInfoCache { 92 | 93 | if let textField = modal.textFieldView as? UITextField { 94 | textField.returnKeyType = modal.originalReturnKeyType 95 | 96 | textField.delegate = modal.textFieldDelegate 97 | 98 | } else if let textView = modal.textFieldView as? UITextView { 99 | 100 | textView.returnKeyType = modal.originalReturnKeyType 101 | 102 | textView.delegate = modal.textViewDelegate 103 | } 104 | } 105 | 106 | textFieldInfoCache.removeAll() 107 | } 108 | 109 | ///------------------------ 110 | /// MARK: Private variables 111 | ///------------------------ 112 | private var textFieldInfoCache = [IQTextFieldViewInfoModal]() 113 | 114 | ///------------------------ 115 | /// MARK: Private Functions 116 | ///------------------------ 117 | private func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModal? { 118 | 119 | for modal in textFieldInfoCache { 120 | 121 | if let view = modal.textFieldView { 122 | 123 | if view == textField { 124 | return modal 125 | } 126 | } 127 | } 128 | 129 | return nil 130 | } 131 | 132 | private func updateReturnKeyTypeOnTextField(_ view: UIView) { 133 | var superConsideredView: UIView? 134 | 135 | //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347) 136 | for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses { 137 | 138 | superConsideredView = view.superviewOfClassType(disabledClass) 139 | 140 | if superConsideredView != nil { 141 | break 142 | } 143 | } 144 | 145 | var textFields = [UIView]() 146 | 147 | //If there is a tableView in view's hierarchy, then fetching all it's subview that responds. 148 | if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22) 149 | textFields = unwrappedTableView.deepResponderViews() 150 | } else { //Otherwise fetching all the siblings 151 | 152 | textFields = view.responderSiblings() 153 | 154 | //Sorting textFields according to behaviour 155 | switch IQKeyboardManager.shared.toolbarManageBehaviour { 156 | //If needs to sort it by tag 157 | case .byTag: textFields = textFields.sortedArrayByTag() 158 | //If needs to sort it by Position 159 | case .byPosition: textFields = textFields.sortedArrayByPosition() 160 | default: break 161 | } 162 | } 163 | 164 | if let lastView = textFields.last { 165 | 166 | if let textField = view as? UITextField { 167 | 168 | //If it's the last textField in responder view, else next 169 | textField.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next 170 | } else if let textView = view as? UITextView { 171 | 172 | //If it's the last textField in responder view, else next 173 | textView.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next 174 | } 175 | } 176 | } 177 | 178 | ///---------------------------------------------- 179 | /// MARK: Registering/Unregistering textFieldView 180 | ///---------------------------------------------- 181 | 182 | /** 183 | Should pass UITextField/UITextView intance. Assign textFieldView delegate to self, change it's returnKeyType. 184 | 185 | @param view UITextField/UITextView object to register. 186 | */ 187 | @objc public func addTextFieldView(_ view: UIView) { 188 | 189 | let modal = IQTextFieldViewInfoModal(textFieldView: view, textFieldDelegate: nil, textViewDelegate: nil) 190 | 191 | if let textField = view as? UITextField { 192 | 193 | modal.originalReturnKeyType = textField.returnKeyType 194 | modal.textFieldDelegate = textField.delegate 195 | textField.delegate = self 196 | 197 | } else if let textView = view as? UITextView { 198 | 199 | modal.originalReturnKeyType = textView.returnKeyType 200 | modal.textViewDelegate = textView.delegate 201 | textView.delegate = self 202 | } 203 | 204 | textFieldInfoCache.append(modal) 205 | } 206 | 207 | /** 208 | Should pass UITextField/UITextView intance. Restore it's textFieldView delegate and it's returnKeyType. 209 | 210 | @param view UITextField/UITextView object to unregister. 211 | */ 212 | @objc public func removeTextFieldView(_ view: UIView) { 213 | 214 | if let modal = textFieldViewCachedInfo(view) { 215 | 216 | if let textField = view as? UITextField { 217 | 218 | textField.returnKeyType = modal.originalReturnKeyType 219 | textField.delegate = modal.textFieldDelegate 220 | } else if let textView = view as? UITextView { 221 | 222 | textView.returnKeyType = modal.originalReturnKeyType 223 | textView.delegate = modal.textViewDelegate 224 | } 225 | 226 | if let index = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) { 227 | 228 | textFieldInfoCache.remove(at: index) 229 | } 230 | } 231 | } 232 | 233 | /** 234 | Add all the UITextField/UITextView responderView's. 235 | 236 | @param view UIView object to register all it's responder subviews. 237 | */ 238 | @objc public func addResponderFromView(_ view: UIView) { 239 | 240 | let textFields = view.deepResponderViews() 241 | 242 | for textField in textFields { 243 | 244 | addTextFieldView(textField) 245 | } 246 | } 247 | 248 | /** 249 | Remove all the UITextField/UITextView responderView's. 250 | 251 | @param view UIView object to unregister all it's responder subviews. 252 | */ 253 | @objc public func removeResponderFromView(_ view: UIView) { 254 | 255 | let textFields = view.deepResponderViews() 256 | 257 | for textField in textFields { 258 | 259 | removeTextFieldView(textField) 260 | } 261 | } 262 | 263 | @discardableResult private func goToNextResponderOrResign(_ view: UIView) -> Bool { 264 | 265 | var superConsideredView: UIView? 266 | 267 | //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347) 268 | for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses { 269 | 270 | superConsideredView = view.superviewOfClassType(disabledClass) 271 | 272 | if superConsideredView != nil { 273 | break 274 | } 275 | } 276 | 277 | var textFields = [UIView]() 278 | 279 | //If there is a tableView in view's hierarchy, then fetching all it's subview that responds. 280 | if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22) 281 | textFields = unwrappedTableView.deepResponderViews() 282 | } else { //Otherwise fetching all the siblings 283 | 284 | textFields = view.responderSiblings() 285 | 286 | //Sorting textFields according to behaviour 287 | switch IQKeyboardManager.shared.toolbarManageBehaviour { 288 | //If needs to sort it by tag 289 | case .byTag: textFields = textFields.sortedArrayByTag() 290 | //If needs to sort it by Position 291 | case .byPosition: textFields = textFields.sortedArrayByPosition() 292 | default: 293 | break 294 | } 295 | } 296 | 297 | //Getting index of current textField. 298 | if let index = textFields.firstIndex(of: view) { 299 | //If it is not last textField. then it's next object becomeFirstResponder. 300 | if index < (textFields.count - 1) { 301 | 302 | let nextTextField = textFields[index+1] 303 | nextTextField.becomeFirstResponder() 304 | return false 305 | } else { 306 | 307 | view.resignFirstResponder() 308 | return true 309 | } 310 | } else { 311 | return true 312 | } 313 | } 314 | 315 | ///--------------------------------------- 316 | /// MARK: UITextField/UITextView delegates 317 | ///--------------------------------------- 318 | 319 | @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { 320 | 321 | if delegate == nil { 322 | 323 | if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { 324 | if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) { 325 | return unwrapDelegate.textFieldShouldBeginEditing?(textField) == true 326 | } 327 | } 328 | } 329 | 330 | return true 331 | } 332 | 333 | @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { 334 | 335 | if delegate == nil { 336 | 337 | if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { 338 | if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) { 339 | return unwrapDelegate.textFieldShouldEndEditing?(textField) == true 340 | } 341 | } 342 | } 343 | 344 | return true 345 | } 346 | 347 | @objc public func textFieldDidBeginEditing(_ textField: UITextField) { 348 | updateReturnKeyTypeOnTextField(textField) 349 | 350 | var aDelegate: UITextFieldDelegate? = delegate 351 | 352 | if aDelegate == nil { 353 | 354 | if let modal = textFieldViewCachedInfo(textField) { 355 | aDelegate = modal.textFieldDelegate 356 | } 357 | } 358 | 359 | aDelegate?.textFieldDidBeginEditing?(textField) 360 | } 361 | 362 | @objc public func textFieldDidEndEditing(_ textField: UITextField) { 363 | 364 | var aDelegate: UITextFieldDelegate? = delegate 365 | 366 | if aDelegate == nil { 367 | 368 | if let modal = textFieldViewCachedInfo(textField) { 369 | aDelegate = modal.textFieldDelegate 370 | } 371 | } 372 | 373 | aDelegate?.textFieldDidEndEditing?(textField) 374 | } 375 | 376 | #if swift(>=4.2) 377 | @available(iOS 10.0, *) 378 | @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) { 379 | 380 | var aDelegate: UITextFieldDelegate? = delegate 381 | 382 | if aDelegate == nil { 383 | 384 | if let modal = textFieldViewCachedInfo(textField) { 385 | aDelegate = modal.textFieldDelegate 386 | } 387 | } 388 | 389 | aDelegate?.textFieldDidEndEditing?(textField, reason: reason) 390 | } 391 | #else 392 | @available(iOS 10.0, *) 393 | @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) { 394 | 395 | var aDelegate: UITextFieldDelegate? = delegate 396 | 397 | if aDelegate == nil { 398 | 399 | if let modal = textFieldViewCachedInfo(textField) { 400 | aDelegate = modal.textFieldDelegate 401 | } 402 | } 403 | 404 | aDelegate?.textFieldDidEndEditing?(textField, reason: reason) 405 | } 406 | #endif 407 | 408 | @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 409 | 410 | if delegate == nil { 411 | 412 | if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { 413 | if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) { 414 | return unwrapDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == true 415 | } 416 | } 417 | } 418 | return true 419 | } 420 | 421 | @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { 422 | 423 | if delegate == nil { 424 | 425 | if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { 426 | if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) { 427 | return unwrapDelegate.textFieldShouldClear?(textField) == true 428 | } 429 | } 430 | } 431 | 432 | return true 433 | } 434 | 435 | @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { 436 | 437 | var shouldReturn = true 438 | 439 | if delegate == nil { 440 | 441 | if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { 442 | if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) { 443 | shouldReturn = unwrapDelegate.textFieldShouldReturn?(textField) == true 444 | } 445 | } 446 | } 447 | 448 | if shouldReturn == true { 449 | goToNextResponderOrResign(textField) 450 | return true 451 | } else { 452 | return goToNextResponderOrResign(textField) 453 | } 454 | } 455 | 456 | @objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { 457 | 458 | if delegate == nil { 459 | 460 | if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { 461 | if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldBeginEditing(_:))) { 462 | return unwrapDelegate.textViewShouldBeginEditing?(textView) == true 463 | } 464 | } 465 | } 466 | 467 | return true 468 | } 469 | 470 | @objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool { 471 | 472 | if delegate == nil { 473 | 474 | if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { 475 | if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldEndEditing(_:))) { 476 | return unwrapDelegate.textViewShouldEndEditing?(textView) == true 477 | } 478 | } 479 | } 480 | 481 | return true 482 | } 483 | 484 | @objc public func textViewDidBeginEditing(_ textView: UITextView) { 485 | updateReturnKeyTypeOnTextField(textView) 486 | 487 | var aDelegate: UITextViewDelegate? = delegate 488 | 489 | if aDelegate == nil { 490 | 491 | if let modal = textFieldViewCachedInfo(textView) { 492 | aDelegate = modal.textViewDelegate 493 | } 494 | } 495 | 496 | aDelegate?.textViewDidBeginEditing?(textView) 497 | } 498 | 499 | @objc public func textViewDidEndEditing(_ textView: UITextView) { 500 | 501 | var aDelegate: UITextViewDelegate? = delegate 502 | 503 | if aDelegate == nil { 504 | 505 | if let modal = textFieldViewCachedInfo(textView) { 506 | aDelegate = modal.textViewDelegate 507 | } 508 | } 509 | 510 | aDelegate?.textViewDidEndEditing?(textView) 511 | } 512 | 513 | @objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { 514 | 515 | var shouldReturn = true 516 | 517 | if delegate == nil { 518 | 519 | if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { 520 | if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:))) { 521 | shouldReturn = (unwrapDelegate.textView?(textView, shouldChangeTextIn: range, replacementText: text)) == true 522 | } 523 | } 524 | } 525 | 526 | if shouldReturn == true && text == "\n" { 527 | shouldReturn = goToNextResponderOrResign(textView) 528 | } 529 | 530 | return shouldReturn 531 | } 532 | 533 | @objc public func textViewDidChange(_ textView: UITextView) { 534 | 535 | var aDelegate: UITextViewDelegate? = delegate 536 | 537 | if aDelegate == nil { 538 | 539 | if let modal = textFieldViewCachedInfo(textView) { 540 | aDelegate = modal.textViewDelegate 541 | } 542 | } 543 | 544 | aDelegate?.textViewDidChange?(textView) 545 | } 546 | 547 | @objc public func textViewDidChangeSelection(_ textView: UITextView) { 548 | 549 | var aDelegate: UITextViewDelegate? = delegate 550 | 551 | if aDelegate == nil { 552 | 553 | if let modal = textFieldViewCachedInfo(textView) { 554 | aDelegate = modal.textViewDelegate 555 | } 556 | } 557 | 558 | aDelegate?.textViewDidChangeSelection?(textView) 559 | } 560 | 561 | @available(iOS 10.0, *) 562 | @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { 563 | 564 | if delegate == nil { 565 | 566 | if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { 567 | if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange, UITextItemInteraction) -> Bool)) { 568 | return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange, interaction: interaction) == true 569 | } 570 | } 571 | } 572 | 573 | return true 574 | } 575 | 576 | @available(iOS 10.0, *) 577 | @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { 578 | 579 | if delegate == nil { 580 | 581 | if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { 582 | if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) -> Bool)) { 583 | return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) == true 584 | } 585 | } 586 | } 587 | 588 | return true 589 | } 590 | 591 | @available(iOS, deprecated: 10.0) 592 | @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { 593 | 594 | if delegate == nil { 595 | 596 | if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { 597 | if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) { 598 | return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange) == true 599 | } 600 | } 601 | } 602 | 603 | return true 604 | } 605 | 606 | @available(iOS, deprecated: 10.0) 607 | @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool { 608 | 609 | if delegate == nil { 610 | 611 | if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { 612 | if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) { 613 | return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange) == true 614 | } 615 | } 616 | } 617 | 618 | return true 619 | } 620 | } 621 | -------------------------------------------------------------------------------- /IQTextView/IQTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQTextView.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | /** @abstract UITextView with placeholder support */ 27 | open class IQTextView: UITextView { 28 | 29 | @objc required public init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | 32 | #if swift(>=4.2) 33 | let UITextViewTextDidChange = UITextView.textDidChangeNotification 34 | #else 35 | let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange 36 | #endif 37 | 38 | NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self) 39 | } 40 | 41 | @objc override public init(frame: CGRect, textContainer: NSTextContainer?) { 42 | super.init(frame: frame, textContainer: textContainer) 43 | 44 | #if swift(>=4.2) 45 | let notificationName = UITextView.textDidChangeNotification 46 | #else 47 | let notificationName = Notification.Name.UITextViewTextDidChange 48 | #endif 49 | 50 | NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: notificationName, object: self) 51 | } 52 | 53 | @objc override open func awakeFromNib() { 54 | super.awakeFromNib() 55 | 56 | #if swift(>=4.2) 57 | let UITextViewTextDidChange = UITextView.textDidChangeNotification 58 | #else 59 | let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange 60 | #endif 61 | 62 | NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self) 63 | } 64 | 65 | deinit { 66 | placeholderLabel.removeFromSuperview() 67 | NotificationCenter.default.removeObserver(self) 68 | } 69 | 70 | private var placeholderInsets: UIEdgeInsets { 71 | return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding) 72 | } 73 | 74 | private var placeholderExpectedFrame: CGRect { 75 | let placeholderInsets = self.placeholderInsets 76 | let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right 77 | let expectedSize = placeholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom)) 78 | 79 | return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height) 80 | } 81 | 82 | lazy var placeholderLabel: UILabel = { 83 | let label = UILabel() 84 | 85 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 86 | label.lineBreakMode = .byWordWrapping 87 | label.numberOfLines = 0 88 | label.font = self.font 89 | label.textAlignment = self.textAlignment 90 | label.backgroundColor = UIColor.clear 91 | label.textColor = UIColor(white: 0.7, alpha: 1.0) 92 | label.alpha = 0 93 | self.addSubview(label) 94 | 95 | return label 96 | }() 97 | 98 | /** @abstract To set textView's placeholder text color. */ 99 | @IBInspectable open var placeholderTextColor: UIColor? { 100 | 101 | get { 102 | return placeholderLabel.textColor 103 | } 104 | 105 | set { 106 | placeholderLabel.textColor = newValue 107 | } 108 | } 109 | 110 | /** @abstract To set textView's placeholder text. Default is nil. */ 111 | @IBInspectable open var placeholder: String? { 112 | 113 | get { 114 | return placeholderLabel.text 115 | } 116 | 117 | set { 118 | placeholderLabel.text = newValue 119 | refreshPlaceholder() 120 | } 121 | } 122 | 123 | /** @abstract To set textView's placeholder attributed text. Default is nil. */ 124 | open var attributedPlaceholder: NSAttributedString? { 125 | get { 126 | return placeholderLabel.attributedText 127 | } 128 | 129 | set { 130 | placeholderLabel.attributedText = newValue 131 | refreshPlaceholder() 132 | } 133 | } 134 | 135 | @objc override open func layoutSubviews() { 136 | super.layoutSubviews() 137 | 138 | placeholderLabel.frame = placeholderExpectedFrame 139 | } 140 | 141 | @objc internal func refreshPlaceholder() { 142 | 143 | if !text.isEmpty || !attributedText.string.isEmpty { 144 | placeholderLabel.alpha = 0 145 | } else { 146 | placeholderLabel.alpha = 1 147 | } 148 | } 149 | 150 | @objc override open var text: String! { 151 | 152 | didSet { 153 | refreshPlaceholder() 154 | } 155 | } 156 | 157 | open override var attributedText: NSAttributedString! { 158 | 159 | didSet { 160 | refreshPlaceholder() 161 | } 162 | } 163 | 164 | @objc override open var font: UIFont? { 165 | 166 | didSet { 167 | 168 | if let unwrappedFont = font { 169 | placeholderLabel.font = unwrappedFont 170 | } else { 171 | placeholderLabel.font = UIFont.systemFont(ofSize: 12) 172 | } 173 | } 174 | } 175 | 176 | @objc override open var textAlignment: NSTextAlignment { 177 | didSet { 178 | placeholderLabel.textAlignment = textAlignment 179 | } 180 | } 181 | 182 | @objc override open var delegate: UITextViewDelegate? { 183 | 184 | get { 185 | refreshPlaceholder() 186 | return super.delegate 187 | } 188 | 189 | set { 190 | super.delegate = newValue 191 | } 192 | } 193 | 194 | @objc override open var intrinsicContentSize: CGSize { 195 | guard !hasText else { 196 | return super.intrinsicContentSize 197 | } 198 | 199 | var newSize = super.intrinsicContentSize 200 | let placeholderInsets = self.placeholderInsets 201 | newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom 202 | 203 | return newSize 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /IQToolbar/IQBarButtonItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQBarButtonItem.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | import Foundation 26 | 27 | open class IQBarButtonItem: UIBarButtonItem { 28 | 29 | private static var _classInitialize: Void = classInitialize() 30 | 31 | @objc public override init() { 32 | _ = IQBarButtonItem._classInitialize 33 | super.init() 34 | } 35 | 36 | @objc public required init?(coder aDecoder: NSCoder) { 37 | _ = IQBarButtonItem._classInitialize 38 | super.init(coder: aDecoder) 39 | } 40 | 41 | private class func classInitialize() { 42 | 43 | let appearanceProxy = self.appearance() 44 | 45 | #if swift(>=4.2) 46 | let states: [UIControl.State] 47 | #else 48 | let states: [UIControlState] 49 | #endif 50 | 51 | states = [.normal, .highlighted, .disabled, .selected, .application, .reserved] 52 | 53 | for state in states { 54 | 55 | appearanceProxy.setBackgroundImage(nil, for: state, barMetrics: .default) 56 | appearanceProxy.setBackgroundImage(nil, for: state, style: .done, barMetrics: .default) 57 | appearanceProxy.setBackgroundImage(nil, for: state, style: .plain, barMetrics: .default) 58 | appearanceProxy.setBackButtonBackgroundImage(nil, for: state, barMetrics: .default) 59 | } 60 | 61 | appearanceProxy.setTitlePositionAdjustment(UIOffset(), for: .default) 62 | appearanceProxy.setBackgroundVerticalPositionAdjustment(0, for: .default) 63 | appearanceProxy.setBackButtonBackgroundVerticalPositionAdjustment(0, for: .default) 64 | } 65 | 66 | @objc override open var tintColor: UIColor? { 67 | didSet { 68 | 69 | #if swift(>=4.2) 70 | var textAttributes = [NSAttributedString.Key: Any]() 71 | let foregroundColorKey = NSAttributedString.Key.foregroundColor 72 | #elseif swift(>=4) 73 | var textAttributes = [NSAttributedStringKey: Any]() 74 | let foregroundColorKey = NSAttributedStringKey.foregroundColor 75 | #else 76 | var textAttributes = [String: Any]() 77 | let foregroundColorKey = NSForegroundColorAttributeName 78 | #endif 79 | 80 | textAttributes[foregroundColorKey] = tintColor 81 | 82 | #if swift(>=4) 83 | 84 | if let attributes = titleTextAttributes(for: .normal) { 85 | 86 | for (key, value) in attributes { 87 | #if swift(>=4.2) 88 | textAttributes[key] = value 89 | #else 90 | textAttributes[NSAttributedStringKey.init(key)] = value 91 | #endif 92 | } 93 | } 94 | 95 | #else 96 | 97 | if let attributes = titleTextAttributes(for: .normal) { 98 | textAttributes = attributes 99 | } 100 | #endif 101 | 102 | setTitleTextAttributes(textAttributes, for: .normal) 103 | } 104 | } 105 | 106 | /** 107 | Boolean to know if it's a system item or custom item, we are having a limitation that we cannot override a designated initializer, so we are manually setting this property once in initialization 108 | */ 109 | @objc internal var isSystemItem = false 110 | 111 | /** 112 | Additional target & action to do get callback action. Note that setting custom target & selector doesn't affect native functionality, this is just an additional target to get a callback. 113 | 114 | @param target Target object. 115 | @param action Target Selector. 116 | */ 117 | @objc open func setTarget(_ target: AnyObject?, action: Selector?) { 118 | if let target = target, let action = action { 119 | invocation = IQInvocation(target, action) 120 | } else { 121 | invocation = nil 122 | } 123 | } 124 | 125 | /** 126 | Customized Invocation to be called when button is pressed. invocation is internally created using setTarget:action: method. 127 | */ 128 | @objc open var invocation: IQInvocation? 129 | 130 | deinit { 131 | target = nil 132 | invocation = nil 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /IQToolbar/IQInvocation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQInvocation.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | @objc public class IQInvocation: NSObject { 27 | @objc public weak var target: AnyObject? 28 | @objc public var action: Selector 29 | 30 | @objc public init(_ target: AnyObject, _ action: Selector) { 31 | self.target = target 32 | self.action = action 33 | } 34 | 35 | @objc public func invoke(from: Any) { 36 | if let target = target { 37 | UIApplication.shared.sendAction(action, to: target, from: from, for: UIEvent()) 38 | } 39 | } 40 | 41 | deinit { 42 | target = nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /IQToolbar/IQPreviousNextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQPreviousNextView.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | @objc public class IQPreviousNextView: UIView { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /IQToolbar/IQTitleBarButtonItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQTitleBarButtonItem.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import Foundation 25 | import UIKit 26 | 27 | open class IQTitleBarButtonItem: IQBarButtonItem { 28 | 29 | @objc open var titleFont: UIFont? { 30 | 31 | didSet { 32 | if let unwrappedFont = titleFont { 33 | titleButton?.titleLabel?.font = unwrappedFont 34 | } else { 35 | titleButton?.titleLabel?.font = UIFont.systemFont(ofSize: 13) 36 | } 37 | } 38 | } 39 | 40 | @objc override open var title: String? { 41 | didSet { 42 | titleButton?.setTitle(title, for: .normal) 43 | } 44 | } 45 | 46 | /** 47 | titleColor to be used for displaying button text when displaying title (disabled state). 48 | */ 49 | @objc open var titleColor: UIColor? { 50 | 51 | didSet { 52 | 53 | if let color = titleColor { 54 | titleButton?.setTitleColor(color, for: .disabled) 55 | } else { 56 | titleButton?.setTitleColor(UIColor.lightGray, for: .disabled) 57 | } 58 | } 59 | } 60 | 61 | /** 62 | selectableTitleColor to be used for displaying button text when button is enabled. 63 | */ 64 | @objc open var selectableTitleColor: UIColor? { 65 | 66 | didSet { 67 | 68 | if let color = selectableTitleColor { 69 | titleButton?.setTitleColor(color, for: .normal) 70 | } else { 71 | titleButton?.setTitleColor(UIColor.init(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) 72 | } 73 | } 74 | } 75 | 76 | /** 77 | Customized Invocation to be called on title button action. titleInvocation is internally created using setTitleTarget:action: method. 78 | */ 79 | @objc override open var invocation: IQInvocation? { 80 | 81 | didSet { 82 | 83 | if let target = invocation?.target, let action = invocation?.action { 84 | self.isEnabled = true 85 | titleButton?.isEnabled = true 86 | titleButton?.addTarget(target, action: action, for: .touchUpInside) 87 | } else { 88 | self.isEnabled = false 89 | titleButton?.isEnabled = false 90 | titleButton?.removeTarget(nil, action: nil, for: .touchUpInside) 91 | } 92 | } 93 | } 94 | 95 | internal var titleButton: UIButton? 96 | private var _titleView: UIView? 97 | 98 | override init() { 99 | super.init() 100 | } 101 | 102 | @objc public convenience init(title: String?) { 103 | 104 | self.init(title: nil, style: .plain, target: nil, action: nil) 105 | 106 | _titleView = UIView() 107 | _titleView?.backgroundColor = UIColor.clear 108 | 109 | titleButton = UIButton(type: .system) 110 | titleButton?.isEnabled = false 111 | titleButton?.titleLabel?.numberOfLines = 3 112 | titleButton?.setTitleColor(UIColor.lightGray, for: .disabled) 113 | titleButton?.setTitleColor(UIColor.init(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) 114 | titleButton?.backgroundColor = UIColor.clear 115 | titleButton?.titleLabel?.textAlignment = .center 116 | titleButton?.setTitle(title, for: .normal) 117 | titleFont = UIFont.systemFont(ofSize: 13.0) 118 | titleButton?.titleLabel?.font = self.titleFont 119 | _titleView?.addSubview(titleButton!) 120 | 121 | #if swift(>=3.2) 122 | if #available(iOS 11, *) { 123 | 124 | var layoutDefaultLowPriority: UILayoutPriority 125 | var layoutDefaultHighPriority: UILayoutPriority 126 | 127 | #if swift(>=4.0) 128 | let layoutPriorityLowValue = UILayoutPriority.defaultLow.rawValue-1 129 | let layoutPriorityHighValue = UILayoutPriority.defaultHigh.rawValue-1 130 | layoutDefaultLowPriority = UILayoutPriority(rawValue: layoutPriorityLowValue) 131 | layoutDefaultHighPriority = UILayoutPriority(rawValue: layoutPriorityHighValue) 132 | #else 133 | layoutDefaultLowPriority = UILayoutPriorityDefaultLow-1 134 | layoutDefaultHighPriority = UILayoutPriorityDefaultHigh-1 135 | #endif 136 | 137 | _titleView?.translatesAutoresizingMaskIntoConstraints = false 138 | _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical) 139 | _titleView?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal) 140 | _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical) 141 | _titleView?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal) 142 | 143 | titleButton?.translatesAutoresizingMaskIntoConstraints = false 144 | titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .vertical) 145 | titleButton?.setContentHuggingPriority(layoutDefaultLowPriority, for: .horizontal) 146 | titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .vertical) 147 | titleButton?.setContentCompressionResistancePriority(layoutDefaultHighPriority, for: .horizontal) 148 | 149 | let top = NSLayoutConstraint.init(item: titleButton!, attribute: .top, relatedBy: .equal, toItem: _titleView, attribute: .top, multiplier: 1, constant: 0) 150 | let bottom = NSLayoutConstraint.init(item: titleButton!, attribute: .bottom, relatedBy: .equal, toItem: _titleView, attribute: .bottom, multiplier: 1, constant: 0) 151 | let leading = NSLayoutConstraint.init(item: titleButton!, attribute: .leading, relatedBy: .equal, toItem: _titleView, attribute: .leading, multiplier: 1, constant: 0) 152 | let trailing = NSLayoutConstraint.init(item: titleButton!, attribute: .trailing, relatedBy: .equal, toItem: _titleView, attribute: .trailing, multiplier: 1, constant: 0) 153 | 154 | _titleView?.addConstraints([top, bottom, leading, trailing]) 155 | } else { 156 | _titleView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] 157 | titleButton?.autoresizingMask = [.flexibleWidth, .flexibleHeight] 158 | } 159 | #else 160 | _titleView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] 161 | titleButton?.autoresizingMask = [.flexibleWidth, .flexibleHeight] 162 | #endif 163 | 164 | customView = _titleView 165 | } 166 | 167 | @objc required public init?(coder aDecoder: NSCoder) { 168 | super.init(coder: aDecoder) 169 | } 170 | 171 | deinit { 172 | customView = nil 173 | titleButton?.removeTarget(nil, action: nil, for: .touchUpInside) 174 | _titleView = nil 175 | titleButton = nil 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /IQToolbar/IQToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQToolbar.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | /** @abstract IQToolbar for IQKeyboardManager. */ 27 | open class IQToolbar: UIToolbar, UIInputViewAudioFeedback { 28 | 29 | private static var _classInitialize: Void = classInitialize() 30 | 31 | private class func classInitialize() { 32 | 33 | let appearanceProxy = self.appearance() 34 | 35 | appearanceProxy.barTintColor = nil 36 | 37 | let positions: [UIBarPosition] = [.any, .bottom, .top, .topAttached] 38 | 39 | for position in positions { 40 | 41 | appearanceProxy.setBackgroundImage(nil, forToolbarPosition: position, barMetrics: .default) 42 | appearanceProxy.setShadowImage(nil, forToolbarPosition: .any) 43 | } 44 | 45 | //Background color 46 | appearanceProxy.backgroundColor = nil 47 | } 48 | 49 | /** 50 | Previous bar button of toolbar. 51 | */ 52 | private var privatePreviousBarButton: IQBarButtonItem? 53 | @objc open var previousBarButton: IQBarButtonItem { 54 | get { 55 | if privatePreviousBarButton == nil { 56 | privatePreviousBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil) 57 | privatePreviousBarButton?.accessibilityLabel = "Previous" 58 | } 59 | return privatePreviousBarButton! 60 | } 61 | 62 | set (newValue) { 63 | privatePreviousBarButton = newValue 64 | } 65 | } 66 | 67 | /** 68 | Next bar button of toolbar. 69 | */ 70 | private var privateNextBarButton: IQBarButtonItem? 71 | @objc open var nextBarButton: IQBarButtonItem { 72 | get { 73 | if privateNextBarButton == nil { 74 | privateNextBarButton = IQBarButtonItem(image: nil, style: .plain, target: nil, action: nil) 75 | privateNextBarButton?.accessibilityLabel = "Next" 76 | } 77 | return privateNextBarButton! 78 | } 79 | 80 | set (newValue) { 81 | privateNextBarButton = newValue 82 | } 83 | } 84 | 85 | /** 86 | Title bar button of toolbar. 87 | */ 88 | private var privateTitleBarButton: IQTitleBarButtonItem? 89 | @objc open var titleBarButton: IQTitleBarButtonItem { 90 | get { 91 | if privateTitleBarButton == nil { 92 | privateTitleBarButton = IQTitleBarButtonItem(title: nil) 93 | privateTitleBarButton?.accessibilityLabel = "Title" 94 | } 95 | return privateTitleBarButton! 96 | } 97 | 98 | set (newValue) { 99 | privateTitleBarButton = newValue 100 | } 101 | } 102 | 103 | /** 104 | Done bar button of toolbar. 105 | */ 106 | private var privateDoneBarButton: IQBarButtonItem? 107 | @objc open var doneBarButton: IQBarButtonItem { 108 | get { 109 | if privateDoneBarButton == nil { 110 | privateDoneBarButton = IQBarButtonItem(title: nil, style: .done, target: nil, action: nil) 111 | privateDoneBarButton?.accessibilityLabel = "Done" 112 | } 113 | return privateDoneBarButton! 114 | } 115 | 116 | set (newValue) { 117 | privateDoneBarButton = newValue 118 | } 119 | } 120 | 121 | /** 122 | Fixed space bar button of toolbar. 123 | */ 124 | private var privateFixedSpaceBarButton: IQBarButtonItem? 125 | @objc open var fixedSpaceBarButton: IQBarButtonItem { 126 | get { 127 | if privateFixedSpaceBarButton == nil { 128 | privateFixedSpaceBarButton = IQBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 129 | } 130 | privateFixedSpaceBarButton!.isSystemItem = true 131 | 132 | if #available(iOS 10, *) { 133 | privateFixedSpaceBarButton!.width = 6 134 | } else { 135 | privateFixedSpaceBarButton!.width = 20 136 | } 137 | 138 | return privateFixedSpaceBarButton! 139 | } 140 | 141 | set (newValue) { 142 | privateFixedSpaceBarButton = newValue 143 | } 144 | } 145 | 146 | override init(frame: CGRect) { 147 | _ = IQToolbar._classInitialize 148 | super.init(frame: frame) 149 | 150 | sizeToFit() 151 | 152 | autoresizingMask = .flexibleWidth 153 | self.isTranslucent = true 154 | } 155 | 156 | @objc required public init?(coder aDecoder: NSCoder) { 157 | _ = IQToolbar._classInitialize 158 | super.init(coder: aDecoder) 159 | 160 | sizeToFit() 161 | 162 | autoresizingMask = .flexibleWidth 163 | self.isTranslucent = true 164 | } 165 | 166 | @objc override open func sizeThatFits(_ size: CGSize) -> CGSize { 167 | var sizeThatFit = super.sizeThatFits(size) 168 | sizeThatFit.height = 44 169 | return sizeThatFit 170 | } 171 | 172 | @objc override open var tintColor: UIColor! { 173 | 174 | didSet { 175 | if let unwrappedItems = items { 176 | for item in unwrappedItems { 177 | item.tintColor = tintColor 178 | } 179 | } 180 | } 181 | } 182 | 183 | @objc override open var barStyle: UIBarStyle { 184 | didSet { 185 | 186 | if titleBarButton.selectableTitleColor == nil { 187 | if barStyle == .default { 188 | titleBarButton.titleButton?.setTitleColor(UIColor.init(red: 0.0, green: 0.5, blue: 1.0, alpha: 1), for: .normal) 189 | } else { 190 | titleBarButton.titleButton?.setTitleColor(UIColor.yellow, for: .normal) 191 | } 192 | } 193 | } 194 | } 195 | 196 | @objc override open func layoutSubviews() { 197 | 198 | super.layoutSubviews() 199 | 200 | //If running on Xcode9 (iOS11) only then we'll validate for iOS version, otherwise for older versions of Xcode (iOS10 and below) we'll just execute the tweak 201 | #if swift(>=3.2) 202 | 203 | if #available(iOS 11, *) { 204 | return 205 | } else if let customTitleView = titleBarButton.customView { 206 | var leftRect = CGRect.null 207 | var rightRect = CGRect.null 208 | var isTitleBarButtonFound = false 209 | 210 | let sortedSubviews = self.subviews.sorted(by: { (view1: UIView, view2: UIView) -> Bool in 211 | if view1.frame.minX != view2.frame.minX { 212 | return view1.frame.minX < view2.frame.minX 213 | } else { 214 | return view1.frame.minY < view2.frame.minY 215 | } 216 | }) 217 | 218 | for barButtonItemView in sortedSubviews { 219 | 220 | if isTitleBarButtonFound == true { 221 | rightRect = barButtonItemView.frame 222 | break 223 | } else if barButtonItemView === customTitleView { 224 | isTitleBarButtonFound = true 225 | //If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem) 226 | } else if barButtonItemView.isKind(of: UIControl.self) == true { 227 | leftRect = barButtonItemView.frame 228 | } 229 | } 230 | 231 | let titleMargin: CGFloat = 16 232 | 233 | let maxWidth: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) 234 | let maxHeight = self.frame.height 235 | 236 | let sizeThatFits = customTitleView.sizeThatFits(CGSize(width: maxWidth, height: maxHeight)) 237 | 238 | var titleRect: CGRect 239 | 240 | if sizeThatFits.width > 0 && sizeThatFits.height > 0 { 241 | let width = min(sizeThatFits.width, maxWidth) 242 | let height = min(sizeThatFits.height, maxHeight) 243 | 244 | var xPosition: CGFloat 245 | 246 | if leftRect.isNull == false { 247 | xPosition = titleMargin + leftRect.maxX + ((maxWidth - width)/2) 248 | } else { 249 | xPosition = titleMargin 250 | } 251 | 252 | let yPosition = (maxHeight - height)/2 253 | 254 | titleRect = CGRect(x: xPosition, y: yPosition, width: width, height: height) 255 | } else { 256 | 257 | var xPosition: CGFloat 258 | 259 | if leftRect.isNull == false { 260 | xPosition = titleMargin + leftRect.maxX 261 | } else { 262 | xPosition = titleMargin 263 | } 264 | 265 | let width: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) 266 | 267 | titleRect = CGRect(x: xPosition, y: 0, width: width, height: maxHeight) 268 | } 269 | 270 | customTitleView.frame = titleRect 271 | } 272 | 273 | #else 274 | if let customTitleView = titleBarButton.customView { 275 | var leftRect = CGRect.null 276 | var rightRect = CGRect.null 277 | var isTitleBarButtonFound = false 278 | 279 | let sortedSubviews = self.subviews.sorted(by: { (view1: UIView, view2: UIView) -> Bool in 280 | if view1.frame.minX != view2.frame.minX { 281 | return view1.frame.minX < view2.frame.minX 282 | } else { 283 | return view1.frame.minY < view2.frame.minY 284 | } 285 | }) 286 | 287 | for barButtonItemView in sortedSubviews { 288 | 289 | if isTitleBarButtonFound == true { 290 | rightRect = barButtonItemView.frame 291 | break 292 | } else if barButtonItemView === titleBarButton.customView { 293 | isTitleBarButtonFound = true 294 | //If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem) 295 | } else if barButtonItemView.isKind(of: UIControl.self) == true { 296 | leftRect = barButtonItemView.frame 297 | } 298 | } 299 | 300 | let titleMargin: CGFloat = 16 301 | let maxWidth: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) 302 | let maxHeight = self.frame.height 303 | 304 | let sizeThatFits = customTitleView.sizeThatFits(CGSize(width: maxWidth, height: maxHeight)) 305 | 306 | var titleRect: CGRect 307 | 308 | if sizeThatFits.width > 0 && sizeThatFits.height > 0 { 309 | let width = min(sizeThatFits.width, maxWidth) 310 | let height = min(sizeThatFits.height, maxHeight) 311 | 312 | var xPosition: CGFloat 313 | 314 | if leftRect.isNull == false { 315 | xPosition = titleMargin + leftRect.maxX + ((maxWidth - width)/2) 316 | } else { 317 | xPosition = titleMargin 318 | } 319 | 320 | let yPosition = (maxHeight - height)/2 321 | 322 | titleRect = CGRect(x: xPosition, y: yPosition, width: width, height: height) 323 | } else { 324 | 325 | var xPosition: CGFloat 326 | 327 | if leftRect.isNull == false { 328 | xPosition = titleMargin + leftRect.maxX 329 | } else { 330 | xPosition = titleMargin 331 | } 332 | 333 | let width: CGFloat = self.frame.width - titleMargin*2 - (leftRect.isNull ? 0 : leftRect.maxX) - (rightRect.isNull ? 0 : self.frame.width - rightRect.minX) 334 | 335 | titleRect = CGRect(x: xPosition, y: 0, width: width, height: maxHeight) 336 | } 337 | 338 | customTitleView.frame = titleRect 339 | } 340 | #endif 341 | } 342 | 343 | @objc open var enableInputClicksWhenVisible: Bool { 344 | return true 345 | } 346 | 347 | deinit { 348 | 349 | items = nil 350 | privatePreviousBarButton = nil 351 | privateNextBarButton = nil 352 | privateTitleBarButton = nil 353 | privateDoneBarButton = nil 354 | privateFixedSpaceBarButton = nil 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /IQToolbar/IQUIView+IQKeyboardToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQUIView+IQKeyboardToolbar.swift 3 | // https://github.com/hackiftekhar/IQKeyboardManager 4 | // Copyright (c) 2013-16 Iftekhar Qurashi. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import UIKit 25 | 26 | private var kIQShouldHideToolbarPlaceholder = "kIQShouldHideToolbarPlaceholder" 27 | private var kIQToolbarPlaceholder = "kIQToolbarPlaceholder" 28 | 29 | private var kIQKeyboardToolbar = "kIQKeyboardToolbar" 30 | 31 | /** 32 | IQBarButtonItemConfiguration for creating toolbar with bar button items 33 | */ 34 | @objc public class IQBarButtonItemConfiguration: NSObject { 35 | 36 | #if swift(>=4.2) 37 | @objc public init(barButtonSystemItem: UIBarButtonItem.SystemItem, action: Selector) { 38 | self.barButtonSystemItem = barButtonSystemItem 39 | self.image = nil 40 | self.title = nil 41 | self.action = action 42 | super.init() 43 | } 44 | #else 45 | @objc public init(barButtonSystemItem: UIBarButtonSystemItem, action: Selector) { 46 | self.barButtonSystemItem = barButtonSystemItem 47 | self.image = nil 48 | self.title = nil 49 | self.action = action 50 | super.init() 51 | } 52 | #endif 53 | 54 | @objc public init(image: UIImage, action: Selector) { 55 | self.barButtonSystemItem = nil 56 | self.image = image 57 | self.title = nil 58 | self.action = action 59 | super.init() 60 | } 61 | 62 | @objc public init(title: String, action: Selector) { 63 | self.barButtonSystemItem = nil 64 | self.image = nil 65 | self.title = title 66 | self.action = action 67 | super.init() 68 | } 69 | 70 | #if swift(>=4.2) 71 | public let barButtonSystemItem: UIBarButtonItem.SystemItem? //System Item to be used to instantiate bar button. 72 | #else 73 | public let barButtonSystemItem: UIBarButtonSystemItem? //System Item to be used to instantiate bar button. 74 | #endif 75 | 76 | @objc public let image: UIImage? //Image to show on bar button item if it's not a system item. 77 | 78 | @objc public let title: String? //Title to show on bar button item if it's not a system item. 79 | 80 | @objc public let action: Selector? //action for bar button item. Usually 'doneAction:(IQBarButtonItem*)item'. 81 | } 82 | 83 | /** 84 | UIImage category methods to get next/prev images 85 | */ 86 | @objc public extension UIImage { 87 | 88 | @objc static func keyboardPreviousiOS9Image() -> UIImage? { 89 | 90 | struct Static { 91 | static var keyboardPreviousiOS9Image: UIImage? 92 | } 93 | 94 | if Static.keyboardPreviousiOS9Image == nil { 95 | // Get the top level "bundle" which may actually be the framework 96 | var bundle = Bundle(for: IQKeyboardManager.self) 97 | 98 | if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") { 99 | if let resourcesBundle = Bundle(path: resourcePath) { 100 | bundle = resourcesBundle 101 | } 102 | } 103 | 104 | Static.keyboardPreviousiOS9Image = UIImage(named: "IQButtonBarArrowLeft", in: bundle, compatibleWith: nil) 105 | 106 | //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) 107 | if #available(iOS 9, *) { 108 | Static.keyboardPreviousiOS9Image = Static.keyboardPreviousiOS9Image?.imageFlippedForRightToLeftLayoutDirection() 109 | } 110 | } 111 | 112 | return Static.keyboardPreviousiOS9Image 113 | } 114 | 115 | @objc static func keyboardNextiOS9Image() -> UIImage? { 116 | 117 | struct Static { 118 | static var keyboardNextiOS9Image: UIImage? 119 | } 120 | 121 | if Static.keyboardNextiOS9Image == nil { 122 | // Get the top level "bundle" which may actually be the framework 123 | var bundle = Bundle(for: IQKeyboardManager.self) 124 | 125 | if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") { 126 | if let resourcesBundle = Bundle(path: resourcePath) { 127 | bundle = resourcesBundle 128 | } 129 | } 130 | 131 | Static.keyboardNextiOS9Image = UIImage(named: "IQButtonBarArrowRight", in: bundle, compatibleWith: nil) 132 | 133 | //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) 134 | if #available(iOS 9, *) { 135 | Static.keyboardNextiOS9Image = Static.keyboardNextiOS9Image?.imageFlippedForRightToLeftLayoutDirection() 136 | } 137 | } 138 | 139 | return Static.keyboardNextiOS9Image 140 | } 141 | 142 | @objc static func keyboardPreviousiOS10Image() -> UIImage? { 143 | 144 | struct Static { 145 | static var keyboardPreviousiOS10Image: UIImage? 146 | } 147 | 148 | if Static.keyboardPreviousiOS10Image == nil { 149 | // Get the top level "bundle" which may actually be the framework 150 | var bundle = Bundle(for: IQKeyboardManager.self) 151 | 152 | if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") { 153 | if let resourcesBundle = Bundle(path: resourcePath) { 154 | bundle = resourcesBundle 155 | } 156 | } 157 | 158 | Static.keyboardPreviousiOS10Image = UIImage(named: "IQButtonBarArrowUp", in: bundle, compatibleWith: nil) 159 | 160 | //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) 161 | if #available(iOS 9, *) { 162 | Static.keyboardPreviousiOS10Image = Static.keyboardPreviousiOS10Image?.imageFlippedForRightToLeftLayoutDirection() 163 | } 164 | } 165 | 166 | return Static.keyboardPreviousiOS10Image 167 | } 168 | 169 | @objc static func keyboardNextiOS10Image() -> UIImage? { 170 | 171 | struct Static { 172 | static var keyboardNextiOS10Image: UIImage? 173 | } 174 | 175 | if Static.keyboardNextiOS10Image == nil { 176 | // Get the top level "bundle" which may actually be the framework 177 | var bundle = Bundle(for: IQKeyboardManager.self) 178 | 179 | if let resourcePath = bundle.path(forResource: "IQKeyboardManager", ofType: "bundle") { 180 | if let resourcesBundle = Bundle(path: resourcePath) { 181 | bundle = resourcesBundle 182 | } 183 | } 184 | 185 | Static.keyboardNextiOS10Image = UIImage(named: "IQButtonBarArrowDown", in: bundle, compatibleWith: nil) 186 | 187 | //Support for RTL languages like Arabic, Persia etc... (Bug ID: #448) 188 | if #available(iOS 9, *) { 189 | Static.keyboardNextiOS10Image = Static.keyboardNextiOS10Image?.imageFlippedForRightToLeftLayoutDirection() 190 | } 191 | } 192 | 193 | return Static.keyboardNextiOS10Image 194 | } 195 | 196 | @objc static func keyboardPreviousImage() -> UIImage? { 197 | 198 | if #available(iOS 10, *) { 199 | return keyboardPreviousiOS10Image() 200 | } else { 201 | return keyboardPreviousiOS9Image() 202 | } 203 | } 204 | 205 | @objc static func keyboardNextImage() -> UIImage? { 206 | 207 | if #available(iOS 10, *) { 208 | return keyboardNextiOS10Image() 209 | } else { 210 | return keyboardNextiOS9Image() 211 | } 212 | } 213 | } 214 | 215 | /** 216 | UIView category methods to add IQToolbar on UIKeyboard. 217 | */ 218 | @objc public extension UIView { 219 | 220 | ///-------------- 221 | /// MARK: Toolbar 222 | ///-------------- 223 | 224 | /** 225 | IQToolbar references for better customization control. 226 | */ 227 | @objc var keyboardToolbar: IQToolbar { 228 | var toolbar = inputAccessoryView as? IQToolbar 229 | 230 | if toolbar == nil { 231 | toolbar = objc_getAssociatedObject(self, &kIQKeyboardToolbar) as? IQToolbar 232 | } 233 | 234 | if let unwrappedToolbar = toolbar { 235 | 236 | return unwrappedToolbar 237 | 238 | } else { 239 | 240 | let newToolbar = IQToolbar() 241 | 242 | objc_setAssociatedObject(self, &kIQKeyboardToolbar, newToolbar, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 243 | 244 | return newToolbar 245 | } 246 | } 247 | 248 | ///-------------------- 249 | /// MARK: Toolbar title 250 | ///-------------------- 251 | 252 | /** 253 | If `shouldHideToolbarPlaceholder` is YES, then title will not be added to the toolbar. Default to NO. 254 | */ 255 | @objc var shouldHideToolbarPlaceholder: Bool { 256 | get { 257 | let aValue = objc_getAssociatedObject(self, &kIQShouldHideToolbarPlaceholder) as Any? 258 | 259 | if let unwrapedValue = aValue as? Bool { 260 | return unwrapedValue 261 | } else { 262 | return false 263 | } 264 | } 265 | set(newValue) { 266 | objc_setAssociatedObject(self, &kIQShouldHideToolbarPlaceholder, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 267 | 268 | self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder 269 | } 270 | } 271 | 272 | /** 273 | `toolbarPlaceholder` to override default `placeholder` text when drawing text on toolbar. 274 | */ 275 | @objc var toolbarPlaceholder: String? { 276 | get { 277 | let aValue = objc_getAssociatedObject(self, &kIQToolbarPlaceholder) as? String 278 | 279 | return aValue 280 | } 281 | set(newValue) { 282 | objc_setAssociatedObject(self, &kIQToolbarPlaceholder, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 283 | 284 | self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder 285 | } 286 | } 287 | 288 | /** 289 | `drawingToolbarPlaceholder` will be actual text used to draw on toolbar. This would either `placeholder` or `toolbarPlaceholder`. 290 | */ 291 | @objc var drawingToolbarPlaceholder: String? { 292 | 293 | if self.shouldHideToolbarPlaceholder { 294 | return nil 295 | } else if self.toolbarPlaceholder?.isEmpty == false { 296 | return self.toolbarPlaceholder 297 | } else if self.responds(to: #selector(getter: UITextField.placeholder)) { 298 | 299 | if let textField = self as? UITextField { 300 | return textField.placeholder 301 | } else if let textView = self as? IQTextView { 302 | return textView.placeholder 303 | } else { 304 | return nil 305 | } 306 | } else { 307 | return nil 308 | } 309 | } 310 | 311 | ///--------------------- 312 | /// MARK: Private helper 313 | ///--------------------- 314 | 315 | private static func flexibleBarButtonItem () -> IQBarButtonItem { 316 | 317 | struct Static { 318 | 319 | static let nilButton = IQBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 320 | } 321 | 322 | Static.nilButton.isSystemItem = true 323 | return Static.nilButton 324 | } 325 | 326 | ///------------- 327 | /// MARK: Common 328 | ///------------- 329 | 330 | @objc func addKeyboardToolbarWithTarget(target: AnyObject?, titleText: String?, rightBarButtonConfiguration: IQBarButtonItemConfiguration?, previousBarButtonConfiguration: IQBarButtonItemConfiguration? = nil, nextBarButtonConfiguration: IQBarButtonItemConfiguration? = nil) { 331 | 332 | //If can't set InputAccessoryView. Then return 333 | if self.responds(to: #selector(setter: UITextField.inputAccessoryView)) { 334 | 335 | // Creating a toolBar for phoneNumber keyboard 336 | let toolbar = self.keyboardToolbar 337 | 338 | var items: [IQBarButtonItem] = [] 339 | 340 | if let prevConfig = previousBarButtonConfiguration { 341 | 342 | var prev = toolbar.previousBarButton 343 | 344 | if prevConfig.barButtonSystemItem == nil && prev.isSystemItem == false { 345 | prev.title = prevConfig.title 346 | prev.image = prevConfig.image 347 | prev.target = target 348 | prev.action = prevConfig.action 349 | } else { 350 | if let systemItem = prevConfig.barButtonSystemItem { 351 | prev = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: prevConfig.action) 352 | prev.isSystemItem = true 353 | } else if let image = prevConfig.image { 354 | prev = IQBarButtonItem(image: image, style: .plain, target: target, action: prevConfig.action) 355 | } else { 356 | prev = IQBarButtonItem(title: prevConfig.title, style: .plain, target: target, action: prevConfig.action) 357 | } 358 | 359 | prev.invocation = toolbar.previousBarButton.invocation 360 | prev.accessibilityLabel = toolbar.previousBarButton.accessibilityLabel 361 | prev.isEnabled = toolbar.previousBarButton.isEnabled 362 | prev.tag = toolbar.previousBarButton.tag 363 | toolbar.previousBarButton = prev 364 | } 365 | 366 | items.append(prev) 367 | } 368 | 369 | if previousBarButtonConfiguration != nil && nextBarButtonConfiguration != nil { 370 | 371 | items.append(toolbar.fixedSpaceBarButton) 372 | } 373 | 374 | if let nextConfig = nextBarButtonConfiguration { 375 | 376 | var next = toolbar.nextBarButton 377 | 378 | if nextConfig.barButtonSystemItem == nil && next.isSystemItem == false { 379 | next.title = nextConfig.title 380 | next.image = nextConfig.image 381 | next.target = target 382 | next.action = nextConfig.action 383 | } else { 384 | if let systemItem = nextConfig.barButtonSystemItem { 385 | next = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: nextConfig.action) 386 | next.isSystemItem = true 387 | } else if let image = nextConfig.image { 388 | next = IQBarButtonItem(image: image, style: .plain, target: target, action: nextConfig.action) 389 | } else { 390 | next = IQBarButtonItem(title: nextConfig.title, style: .plain, target: target, action: nextConfig.action) 391 | } 392 | 393 | next.invocation = toolbar.nextBarButton.invocation 394 | next.accessibilityLabel = toolbar.nextBarButton.accessibilityLabel 395 | next.isEnabled = toolbar.nextBarButton.isEnabled 396 | next.tag = toolbar.nextBarButton.tag 397 | toolbar.nextBarButton = next 398 | } 399 | 400 | items.append(next) 401 | } 402 | 403 | //Title bar button item 404 | do { 405 | //Flexible space 406 | items.append(UIView.flexibleBarButtonItem()) 407 | 408 | //Title button 409 | toolbar.titleBarButton.title = titleText 410 | 411 | #if swift(>=3.2) 412 | if #available(iOS 11, *) {} else { 413 | toolbar.titleBarButton.customView?.frame = CGRect.zero 414 | } 415 | #else 416 | toolbar.titleBarButton.customView?.frame = CGRect.zero 417 | #endif 418 | 419 | items.append(toolbar.titleBarButton) 420 | 421 | //Flexible space 422 | items.append(UIView.flexibleBarButtonItem()) 423 | } 424 | 425 | if let rightConfig = rightBarButtonConfiguration { 426 | 427 | var done = toolbar.doneBarButton 428 | 429 | if rightConfig.barButtonSystemItem == nil && done.isSystemItem == false { 430 | done.title = rightConfig.title 431 | done.image = rightConfig.image 432 | done.target = target 433 | done.action = rightConfig.action 434 | } else { 435 | if let systemItem = rightConfig.barButtonSystemItem { 436 | done = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: rightConfig.action) 437 | done.isSystemItem = true 438 | } else if let image = rightConfig.image { 439 | done = IQBarButtonItem(image: image, style: .plain, target: target, action: rightConfig.action) 440 | } else { 441 | done = IQBarButtonItem(title: rightConfig.title, style: .plain, target: target, action: rightConfig.action) 442 | } 443 | 444 | done.invocation = toolbar.doneBarButton.invocation 445 | done.accessibilityLabel = toolbar.doneBarButton.accessibilityLabel 446 | done.isEnabled = toolbar.doneBarButton.isEnabled 447 | done.tag = toolbar.doneBarButton.tag 448 | toolbar.doneBarButton = done 449 | } 450 | 451 | items.append(done) 452 | } 453 | 454 | // Adding button to toolBar. 455 | toolbar.items = items 456 | 457 | // Setting toolbar to keyboard. 458 | if let textField = self as? UITextField { 459 | textField.inputAccessoryView = toolbar 460 | 461 | switch textField.keyboardAppearance { 462 | case .dark: 463 | toolbar.barStyle = UIBarStyle.black 464 | default: 465 | toolbar.barStyle = UIBarStyle.default 466 | } 467 | } else if let textView = self as? UITextView { 468 | textView.inputAccessoryView = toolbar 469 | 470 | switch textView.keyboardAppearance { 471 | case .dark: 472 | toolbar.barStyle = UIBarStyle.black 473 | default: 474 | toolbar.barStyle = UIBarStyle.default 475 | } 476 | } 477 | } 478 | } 479 | 480 | ///------------ 481 | /// MARK: Right 482 | ///------------ 483 | 484 | @objc func addDoneOnKeyboardWithTarget(_ target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) { 485 | 486 | addDoneOnKeyboardWithTarget(target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 487 | } 488 | 489 | @objc func addDoneOnKeyboardWithTarget(_ target: AnyObject?, action: Selector, titleText: String?) { 490 | 491 | let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: action) 492 | 493 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration) 494 | } 495 | 496 | @objc func addRightButtonOnKeyboardWithImage(_ image: UIImage, target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) { 497 | 498 | addRightButtonOnKeyboardWithImage(image, target: target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 499 | } 500 | 501 | @objc func addRightButtonOnKeyboardWithImage(_ image: UIImage, target: AnyObject?, action: Selector, titleText: String?) { 502 | 503 | let rightConfiguration = IQBarButtonItemConfiguration(image: image, action: action) 504 | 505 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration) 506 | } 507 | 508 | @objc func addRightButtonOnKeyboardWithText(_ text: String, target: AnyObject?, action: Selector, shouldShowPlaceholder: Bool = false) { 509 | 510 | addRightButtonOnKeyboardWithText(text, target: target, action: action, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 511 | } 512 | 513 | @objc func addRightButtonOnKeyboardWithText(_ text: String, target: AnyObject?, action: Selector, titleText: String?) { 514 | 515 | let rightConfiguration = IQBarButtonItemConfiguration(title: text, action: action) 516 | 517 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration) 518 | } 519 | 520 | ///----------------- 521 | /// MARK: Right/Left 522 | ///----------------- 523 | 524 | @objc func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?, cancelAction: Selector, doneAction: Selector, shouldShowPlaceholder: Bool = false) { 525 | 526 | addCancelDoneOnKeyboardWithTarget(target, cancelAction: cancelAction, doneAction: doneAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 527 | } 528 | 529 | @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonTitle: String, rightButtonTitle: String, leftButtonAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { 530 | 531 | addRightLeftOnKeyboardWithTarget(target, leftButtonTitle: leftButtonTitle, rightButtonTitle: rightButtonTitle, leftButtonAction: leftButtonAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 532 | } 533 | 534 | @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonImage: UIImage, rightButtonImage: UIImage, leftButtonAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { 535 | 536 | addRightLeftOnKeyboardWithTarget(target, leftButtonImage: leftButtonImage, rightButtonImage: rightButtonImage, leftButtonAction: leftButtonAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 537 | } 538 | 539 | @objc func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?, cancelAction: Selector, doneAction: Selector, titleText: String?) { 540 | 541 | let leftConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .cancel, action: cancelAction) 542 | let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: doneAction) 543 | 544 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration) 545 | } 546 | 547 | @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonTitle: String, rightButtonTitle: String, leftButtonAction: Selector, rightButtonAction: Selector, titleText: String?) { 548 | 549 | let leftConfiguration = IQBarButtonItemConfiguration(title: leftButtonTitle, action: leftButtonAction) 550 | let rightConfiguration = IQBarButtonItemConfiguration(title: rightButtonTitle, action: rightButtonAction) 551 | 552 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration) 553 | } 554 | 555 | @objc func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?, leftButtonImage: UIImage, rightButtonImage: UIImage, leftButtonAction: Selector, rightButtonAction: Selector, titleText: String?) { 556 | 557 | let leftConfiguration = IQBarButtonItemConfiguration(image: leftButtonImage, action: leftButtonAction) 558 | let rightConfiguration = IQBarButtonItemConfiguration(image: rightButtonImage, action: rightButtonAction) 559 | 560 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: leftConfiguration) 561 | } 562 | 563 | ///-------------------------- 564 | /// MARK: Previous/Next/Right 565 | ///-------------------------- 566 | 567 | @objc func addPreviousNextDoneOnKeyboardWithTarget (_ target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector, shouldShowPlaceholder: Bool = false) { 568 | 569 | addPreviousNextDoneOnKeyboardWithTarget(target, previousAction: previousAction, nextAction: nextAction, doneAction: doneAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 570 | } 571 | 572 | @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonImage: UIImage, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { 573 | 574 | addPreviousNextRightOnKeyboardWithTarget(target, rightButtonImage: rightButtonImage, previousAction: previousAction, nextAction: nextAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 575 | } 576 | 577 | @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonTitle: String, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, shouldShowPlaceholder: Bool = false) { 578 | 579 | addPreviousNextRightOnKeyboardWithTarget(target, rightButtonTitle: rightButtonTitle, previousAction: previousAction, nextAction: nextAction, rightButtonAction: rightButtonAction, titleText: (shouldShowPlaceholder ? self.drawingToolbarPlaceholder: nil)) 580 | } 581 | 582 | @objc func addPreviousNextDoneOnKeyboardWithTarget (_ target: AnyObject?, previousAction: Selector, nextAction: Selector, doneAction: Selector, titleText: String?) { 583 | 584 | let rightConfiguration = IQBarButtonItemConfiguration(barButtonSystemItem: .done, action: doneAction) 585 | let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction) 586 | let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction) 587 | 588 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) 589 | } 590 | 591 | @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonImage: UIImage, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, titleText: String?) { 592 | 593 | let rightConfiguration = IQBarButtonItemConfiguration(image: rightButtonImage, action: rightButtonAction) 594 | let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction) 595 | let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction) 596 | 597 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) 598 | } 599 | 600 | @objc func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?, rightButtonTitle: String, previousAction: Selector, nextAction: Selector, rightButtonAction: Selector, titleText: String?) { 601 | 602 | let rightConfiguration = IQBarButtonItemConfiguration(title: rightButtonTitle, action: rightButtonAction) 603 | let nextConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardNextImage() ?? UIImage(), action: nextAction) 604 | let prevConfiguration = IQBarButtonItemConfiguration(image: UIImage.keyboardPreviousImage() ?? UIImage(), action: previousAction) 605 | 606 | addKeyboardToolbarWithTarget(target: target, titleText: titleText, rightBarButtonConfiguration: rightConfiguration, previousBarButtonConfiguration: prevConfiguration, nextBarButtonConfiguration: nextConfiguration) 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IQKeyboardManagerSwift forked from hackiftekhar/IQKeyboardManager. 2 | 3 | Just for easier integration, please support the original author: [hackiftekhar](https://github.com/hackiftekhar/IQKeyboardManager) -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@2x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowDown@3x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@2x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowLeft@3x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@2x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowRight@3x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@2x.png -------------------------------------------------------------------------------- /Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acegg/IQKeyboardManagerSwift/8028e7eb222a173f471a2156f4ceb46b0d3db322/Resources/IQKeyboardManager.bundle/IQButtonBarArrowUp@3x.png --------------------------------------------------------------------------------