├── .swift-version ├── Example ├── Example │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── mklogo.imageset │ │ │ ├── mklogo.png │ │ │ └── Contents.json │ │ ├── ic_bold.imageset │ │ │ ├── icons8-bold.png │ │ │ └── Contents.json │ │ ├── ic_link.imageset │ │ │ ├── icons8-link.png │ │ │ └── Contents.json │ │ ├── ic_list.imageset │ │ │ ├── icons8-list.png │ │ │ └── Contents.json │ │ ├── ic_eye.imageset │ │ │ ├── icons8-visible.png │ │ │ └── Contents.json │ │ ├── ic_italic.imageset │ │ │ ├── icons8-italic.png │ │ │ └── Contents.json │ │ ├── ic_up.imageset │ │ │ ├── icons8-up_arrow.png │ │ │ └── Contents.json │ │ ├── ic_upload.imageset │ │ │ ├── icons8-upload.png │ │ │ └── Contents.json │ │ ├── ic_at.imageset │ │ │ ├── icons8-email_filled.png │ │ │ └── Contents.json │ │ ├── ic_plus.imageset │ │ │ ├── icons8-plus_math.png │ │ │ └── Contents.json │ │ ├── ic_code.imageset │ │ │ ├── icons8-source_code.png │ │ │ └── Contents.json │ │ ├── ic_keyboard.imageset │ │ │ ├── icons8-keyboard.png │ │ │ └── Contents.json │ │ ├── ic_send.imageset │ │ │ ├── icons8-sent_filled.png │ │ │ └── Contents.json │ │ ├── ic_typing.imageset │ │ │ ├── icons8-filled_chat.png │ │ │ └── Contents.json │ │ ├── ic_user.imageset │ │ │ ├── icons8-user_filled.png │ │ │ └── Contents.json │ │ ├── icons8-expand.imageset │ │ │ ├── icons8-expand.png │ │ │ └── Contents.json │ │ ├── ic_hashtag.imageset │ │ │ ├── icons8-hashtag_filled.png │ │ │ └── Contents.json │ │ ├── icons8-collapse.imageset │ │ │ ├── icons8-collapse.png │ │ │ └── Contents.json │ │ ├── ic_camera.imageset │ │ │ ├── icons8-compact_camera_filled.png │ │ │ └── Contents.json │ │ ├── ic_library.imageset │ │ │ ├── icons8-stack_of_photos_filled.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── SplitViewController.swift │ ├── Info.plist │ ├── ImageCell.swift │ ├── Style Examples │ │ ├── FacebookInputBar.swift │ │ ├── iMessageInputBar.swift │ │ ├── GitHawkInputBar.swift │ │ └── SlackInputBar.swift │ ├── TableViewController.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── AppDelegate.swift │ ├── ExampleViewController.swift │ └── Lorem.swift ├── Example.xcworkspace │ ├── xcshareddata │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata ├── Example.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile └── Podfile.lock ├── MessageInputBar.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── nathantannar.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── xcshareddata │ └── xcschemes │ └── MessageInputBar.xcscheme ├── .gitignore ├── Sources ├── MessageInputBar.h ├── Info.plist ├── Protocols │ ├── InputPlugin.swift │ ├── InputItem.swift │ └── MessageInputBarDelegate.swift ├── Views │ ├── SeparatorLine.swift │ └── InputStackView.swift ├── Models │ └── NSConstraintLayoutSet.swift ├── Extensions │ ├── NSMutableAttributedString+Extensions.swift │ └── UIView+Extensions.swift └── Controls │ └── InputBarButtonItem.swift ├── .swiftlint.yml ├── Tests ├── Info.plist ├── InputTextViewTests.swift ├── InputBarItemTests.swift └── MessageInputBarTests.swift ├── LICENSE.md ├── CHANGELOG.md ├── MessageInputBar.podspec ├── Plugins ├── AutocompleteManager │ ├── Models │ │ ├── AutocompleteCompletion.swift │ │ └── AutocompleteSession.swift │ ├── Extensions │ │ ├── NSAttributedStringKey+Extensions.swift │ │ ├── UITextView+Extensions.swift │ │ └── String+Extensions.swift │ ├── Views │ │ ├── AutocompleteTableView.swift │ │ └── AutocompleteCell.swift │ └── Protocols │ │ ├── AutocompleteManagerDataSource.swift │ │ └── AutocompleteManagerDelegate.swift └── AttachmentManager │ ├── Protocols │ ├── AttachmentManagerDataSource.swift │ └── AttachmentManagerDelegate.swift │ ├── Views │ ├── ImageAttachmentCell.swift │ ├── AttachmentCollectionView.swift │ └── AttachmentCell.swift │ └── AttachmentManager.swift ├── CODE_OF_CONDUCT.md ├── CHANGELOG_GUIDELINES.md ├── README.md ├── CONTRIBUTING.md └── Documentation └── MessageInputBar.md /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/mklogo.imageset/mklogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/mklogo.imageset/mklogo.png -------------------------------------------------------------------------------- /Example/Example.xcworkspace/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_bold.imageset/icons8-bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_bold.imageset/icons8-bold.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_link.imageset/icons8-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_link.imageset/icons8-link.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_list.imageset/icons8-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_list.imageset/icons8-list.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_eye.imageset/icons8-visible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_eye.imageset/icons8-visible.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_italic.imageset/icons8-italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_italic.imageset/icons8-italic.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_up.imageset/icons8-up_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_up.imageset/icons8-up_arrow.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_upload.imageset/icons8-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_upload.imageset/icons8-upload.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_at.imageset/icons8-email_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_at.imageset/icons8-email_filled.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_plus.imageset/icons8-plus_math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_plus.imageset/icons8-plus_math.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_code.imageset/icons8-source_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_code.imageset/icons8-source_code.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_keyboard.imageset/icons8-keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_keyboard.imageset/icons8-keyboard.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_send.imageset/icons8-sent_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_send.imageset/icons8-sent_filled.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_typing.imageset/icons8-filled_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_typing.imageset/icons8-filled_chat.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_user.imageset/icons8-user_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_user.imageset/icons8-user_filled.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/icons8-expand.imageset/icons8-expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/icons8-expand.imageset/icons8-expand.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_hashtag.imageset/icons8-hashtag_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_hashtag.imageset/icons8-hashtag_filled.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/icons8-collapse.imageset/icons8-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/icons8-collapse.imageset/icons8-collapse.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_camera.imageset/icons8-compact_camera_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_camera.imageset/icons8-compact_camera_filled.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_library.imageset/icons8-stack_of_photos_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MessageKit/MessageInputBar/HEAD/Example/Example/Assets.xcassets/ic_library.imageset/icons8-stack_of_photos_filled.png -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MessageInputBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | target 'Example' do 4 | use_frameworks! 5 | 6 | pod 'MessageInputBar', :path => '../' 7 | pod 'MessageInputBar/AttachmentManager', :path => '../' 8 | pod 'MessageInputBar/AutocompleteManager', :path => '../' 9 | 10 | end 11 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MessageInputBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/mklogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "mklogo.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_bold.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-bold.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_eye.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-visible.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_link.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-link.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-list.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-up_arrow.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_at.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-email_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_code.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-source_code.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_italic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-italic.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_plus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-plus_math.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_send.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-sent_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_upload.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-upload.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_user.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-user_filled.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_keyboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-keyboard.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_typing.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-filled_chat.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/icons8-collapse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-collapse.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/icons8-expand.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icons8-expand.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_hashtag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-hashtag_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-compact_camera_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/ic_library.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-stack_of_photos_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MessageInputBar.xcodeproj/xcuserdata/nathantannar.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MessageInputBar.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | 21 | ## Other 22 | *.xccheckout 23 | *.moved-aside 24 | *.xcuserstate 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | # CocoaPods 32 | Pods/ 33 | 34 | # Carthage 35 | Carthage 36 | -------------------------------------------------------------------------------- /Sources/MessageInputBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // MessageInputBar.h 3 | // MessageInputBar 4 | // 5 | // Created by Nathan Tannar on 2018-06-03. 6 | // Copyright © 2018 MessageKit. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for MessageInputBar. 12 | FOUNDATION_EXPORT double MessageInputBarVersionNumber; 13 | 14 | //! Project version string for MessageInputBar. 15 | FOUNDATION_EXPORT const unsigned char MessageInputBarVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | 2 | disabled_rules: 3 | - identifier_name 4 | - trailing_whitespace 5 | - line_length 6 | - type_body_length 7 | - file_length 8 | custom_rules: 9 | override_func: # rule identifier 10 | name: "override in func" # rule name. optional. 11 | regex: "override (open|public|private|internal|fileprivate)" # matching pattern 12 | message: "Use like open override or public override instead" # violation message. optional. 13 | severity: warning # violation severity. optional. 14 | opt_in_rules: 15 | - explicit_acl 16 | - explicit_top_level_acl 17 | explicit_acl: error 18 | explicit_top_level_acl: error 19 | included: 20 | - Sources -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MessageInputBar (0.4.0): 3 | - MessageInputBar/Core (= 0.4.0) 4 | - MessageInputBar/AttachmentManager (0.4.0): 5 | - MessageInputBar/Core 6 | - MessageInputBar/AutocompleteManager (0.4.0): 7 | - MessageInputBar/Core 8 | - MessageInputBar/Core (0.4.0) 9 | 10 | DEPENDENCIES: 11 | - MessageInputBar (from `../`) 12 | - MessageInputBar/AttachmentManager (from `../`) 13 | - MessageInputBar/AutocompleteManager (from `../`) 14 | 15 | EXTERNAL SOURCES: 16 | MessageInputBar: 17 | :path: "../" 18 | 19 | SPEC CHECKSUMS: 20 | MessageInputBar: 89b524f5dea5599acbeb9ce17ab269eb3683d683 21 | 22 | PODFILE CHECKSUM: 59ceff645e2e779969697ee2390f43a1a67332f9 23 | 24 | COCOAPODS: 1.5.3 25 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.4.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 MessageKit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog for `MessageInputBar`. Also see the [releases](https://github.com/MessageKit/MessageInputBar/releases) on GitHub. 4 | 5 | -------------------------------------- 6 | 7 | ## Upcoming release 8 | 9 | ### Fixed 10 | 11 | - Fixed a layout invalidation cycle that never ended. [#1](https://github.com/MessageKit/MessageKit/pull/1) by [@nathantannar4](https://github.com/nathantannar4). 12 | 13 | ### Added 14 | 15 | - Added a new property `shouldForceTextViewMaxHeight` to `MessageInputBar` which forces the view to layout at its maximum height. Use `setShouldForceMaxTextViewHeight(to newValue: Bool, animated: Bool)` to set the property. [#1](https://github.com/MessageKit/MessageKit/pull/1) by [@nathantannar4](https://github.com/nathantannar4). 16 | 17 | - **Breaking Change** Added a new protocol `InputItem`. `InputBarButtonItem` now confirms to `InputItem` and the item arrays in `MessageInputBar` are now of type `[InputItem]` for more flexability. [#1](https://github.com/MessageKit/MessageKit/pull/1) by [@nathantannar4](https://github.com/nathantannar4). 18 | 19 | ## [[Prerelease] 0.1.0](https://github.com/MessageKit/MessageInputBar/releases/tag/0.1.0) 20 | 21 | This release forks the development of the `MessageInputBar` from [MessageKit 1.0.0-beta.1](https://github.com/MessageKit/MessageKit/releases/tag/1.0.0-beta.1) 22 | -------------------------------------------------------------------------------- /MessageInputBar.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | # 1 - Specs 4 | s.platform = :ios 5 | s.name = 'MessageInputBar' 6 | s.summary = 'A powerful InputAccessoryView ideal for messaging applications.' 7 | s.homepage = 'https://github.com/MessageKit/MessageInputBar' 8 | s.requires_arc = true 9 | 10 | # 2 - Version 11 | s.version = '0.4.1' 12 | s.pod_target_xcconfig = { 13 | "SWIFT_VERSION" => "4.2", 14 | } 15 | 16 | s.swift_version = '4.2' 17 | 18 | s.ios.deployment_target = '9.0' 19 | s.source = { :git => 'https://github.com/MessageKit/MessageInputBar.git', :tag => s.version } 20 | 21 | # 3 - License 22 | s.license = { :type => "MIT", :file => "LICENSE.md" } 23 | 24 | # 4 - Author 25 | s.social_media_url = 'https://twitter.com/nathantannar4' 26 | s.author = { "Nathan Tannar" => "nathantannar4@gmail.com" } 27 | 28 | # 5 - Source Files 29 | s.default_subspecs = 'Core' 30 | 31 | s.subspec 'Core' do |ss| 32 | ss.source_files = 'Sources/**/*.swift' 33 | end 34 | 35 | s.subspec 'AttachmentManager' do |b| 36 | b.source_files = "Plugins/AttachmentManager/**/*.swift" 37 | b.dependency 'MessageInputBar/Core' 38 | end 39 | 40 | s.subspec 'AutocompleteManager' do |c| 41 | c.source_files = "Plugins/AutocompleteManager/**/*.swift" 42 | c.dependency 'MessageInputBar/Core' 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Models/AutocompleteCompletion.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | public struct AutocompleteCompletion { 28 | 29 | // The String to insert/replace upon autocompletion 30 | public let text: String 31 | 32 | // The context of the completion that you may need later when completed 33 | public let context: [String: Any]? 34 | 35 | public init(text: String, context: [String: Any]? = nil) { 36 | self.text = text 37 | self.context = context 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Example/Example/SplitViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | final class SplitViewController: UISplitViewController { 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | self.delegate = self 32 | } 33 | 34 | } 35 | 36 | extension SplitViewController: UISplitViewControllerDelegate { 37 | 38 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { 39 | return true 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Sources/Protocols/InputPlugin.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /// `InputPlugin` is a protocol that makes integrating plugins to the `MessageInputBar` easy. 28 | public protocol InputPlugin: AnyObject { 29 | 30 | /// Should reload the state if the `InputPlugin` 31 | func reloadData() 32 | 33 | /// Should remove any content that the `InputPlugin` is managing 34 | func invalidate() 35 | 36 | /// Should handle the input of data types that an `InputPlugin` manages 37 | /// 38 | /// - Parameter object: The object to input 39 | func handleInput(of object: AnyObject) -> Bool 40 | } 41 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Extensions/NSAttributedStringKey+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | public extension NSAttributedString.Key { 28 | 29 | /// A key used for referencing which substrings were autocompleted 30 | /// by MessageInputBar.AutocompleteManager 31 | static let autocompleted = NSAttributedString.Key("com.messagekit.autocompletekey") 32 | 33 | /// A key used for referencing the context of autocompleted substrings 34 | /// by MessageInputBar.AutocompleteManager 35 | static let autocompletedContext = NSAttributedString.Key("com.messagekit.autocompletekey.context") 36 | } 37 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MessageInputBar 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarStyle 32 | UIStatusBarStyleLightContent 33 | UIViewControllerBasedStatusBarAppearance 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Views/AutocompleteTableView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | open class AutocompleteTableView: UITableView { 28 | 29 | /// The max visible rows visible in the autocomplete table before the user has to scroll throught them 30 | open var maxVisibleRows = 3 { 31 | didSet { 32 | invalidateIntrinsicContentSize() 33 | } 34 | } 35 | 36 | open override var intrinsicContentSize: CGSize { 37 | 38 | let rows = numberOfRows(inSection: 0) < maxVisibleRows ? numberOfRows(inSection: 0) : maxVisibleRows 39 | return CGSize(width: super.intrinsicContentSize.width, height: (CGFloat(rows) * rowHeight)) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Plugins/AttachmentManager/Protocols/AttachmentManagerDataSource.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | /// AttachmentManagerDataSource is a protocol to passes data to the AttachmentManager 28 | public protocol AttachmentManagerDataSource: AnyObject { 29 | 30 | /// The AttachmentCell for the attachment that is to be inserted into the AttachmentView 31 | /// 32 | /// - Parameters: 33 | /// - manager: The AttachmentManager 34 | /// - attachment: The object 35 | /// - index: The index in the AttachmentView 36 | /// - Returns: An AttachmentCell 37 | func attachmentManager(_ manager: AttachmentManager, cellFor attachment: AttachmentManager.Attachment, at index: Int) -> AttachmentCell 38 | } 39 | -------------------------------------------------------------------------------- /Example/Example/ImageCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | class ImageCell: UICollectionViewCell { 28 | 29 | class var reuseIdentifier: String { 30 | return "ImageCell" 31 | } 32 | 33 | let imageView = UIImageView() 34 | 35 | override init(frame: CGRect) { 36 | super.init(frame: frame) 37 | setupView() 38 | } 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | func setupView() { 45 | addSubview(imageView) 46 | imageView.contentMode = .scaleAspectFit 47 | } 48 | 49 | override func layoutSubviews() { 50 | super.layoutSubviews() 51 | imageView.frame = bounds 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Models/AutocompleteSession.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | /// A class containing data on the `AutocompleteManager`'s session 28 | public class AutocompleteSession { 29 | 30 | public let prefix: String 31 | public let range: NSRange 32 | public var filter: String 33 | public var completion: AutocompleteCompletion? 34 | internal var spaceCounter: Int = 0 35 | 36 | public init?(prefix: String?, range: NSRange?, filter: String?) { 37 | guard let pfx = prefix, let rng = range, let flt = filter else { return nil } 38 | self.prefix = pfx 39 | self.range = rng 40 | self.filter = flt 41 | } 42 | } 43 | 44 | extension AutocompleteSession: Equatable { 45 | 46 | public static func == (lhs: AutocompleteSession, rhs: AutocompleteSession) -> Bool { 47 | return lhs.prefix == rhs.prefix && lhs.range == rhs.range 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Sources/Protocols/InputItem.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /// InputItem is a protocol that links elements to the MessageInputBar to make them reactive 28 | public protocol InputItem: AnyObject { 29 | 30 | /// A reference to the MessageInputBar. Set automatically when inserted into an InputStackView 31 | var messageInputBar: MessageInputBar? { get set } 32 | 33 | /// A reference to the InputStackView that the InputItem is contained in. Set when inserted into an InputStackView 34 | var parentStackViewPosition: InputStackView.Position? { get set } 35 | 36 | /// A hook that is called when the InputTextView's text is changed 37 | func textViewDidChangeAction(with textView: InputTextView) 38 | 39 | /// A hook that is called when the InputTextView is resigned as the first responder 40 | func keyboardEditingEndsAction() 41 | 42 | /// A hook that is called when the InputTextView is made the first responder 43 | func keyboardEditingBeginsAction() 44 | } 45 | -------------------------------------------------------------------------------- /Plugins/AttachmentManager/Views/ImageAttachmentCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | open class ImageAttachmentCell: AttachmentCell { 28 | 29 | // MARK: - Properties 30 | 31 | open override class var reuseIdentifier: String { 32 | return "ImageAttachmentCell" 33 | } 34 | 35 | public let imageView: UIImageView = { 36 | let imageView = UIImageView() 37 | imageView.contentMode = .scaleAspectFill 38 | return imageView 39 | }() 40 | 41 | // MARK: - Initialization 42 | 43 | public override init(frame: CGRect) { 44 | super.init(frame: frame) 45 | setup() 46 | } 47 | 48 | required public init?(coder aDecoder: NSCoder) { 49 | super.init(coder: aDecoder) 50 | setup() 51 | } 52 | 53 | open override func prepareForReuse() { 54 | super.prepareForReuse() 55 | imageView.image = nil 56 | } 57 | 58 | // MARK: - Setup 59 | 60 | private func setup() { 61 | containerView.addSubview(imageView) 62 | imageView.fillSuperview() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | **Examples of unacceptable behavior by participants include:** 14 | 15 | - The use of sexualized language or imagery 16 | - Personal attacks 17 | - Trolling or insulting/derogatory comments 18 | - Public or private harassment 19 | - Publishing other's private information, such as physical or electronic addresses, without explicit permission 20 | - Other unethical or unprofessional conduct 21 | - Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 22 | 23 | By adopting this Code of Conduct, project maintainers commit themselves to 24 | fairly and consistently applying these principles to every aspect of managing 25 | this project. Project maintainers who do not follow or enforce the Code of 26 | Conduct may be permanently removed from the project team. 27 | 28 | This code of conduct applies both within project spaces and in public spaces 29 | when an individual is representing the project or its community. 30 | 31 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 32 | reported by contacting a project maintainer at stevensdeutsch@yahoo.com. 33 | 34 | All complaints will be reviewed and investigated and will result in a response that 35 | is deemed necessary and appropriate to the circumstances. Maintainers are 36 | obligated to maintain confidentiality with regard to the reporter of an 37 | incident. 38 | 39 | This Code of Conduct is adapted from the Contributor Covenant, 40 | version 1.3.0, available at http://contributor-covenant.org/version/1/3/0/ 41 | -------------------------------------------------------------------------------- /Sources/Views/SeparatorLine.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /** 28 | A UIView thats intrinsicContentSize is overrided so an exact height can be specified 29 | 30 | ## Important Notes ## 31 | 1. Default height is 1.0 32 | 2. Default backgroundColor is UIColor.lightGray 33 | 3. Intended to be used in an `InputStackView` 34 | */ 35 | open class SeparatorLine: UIView { 36 | 37 | // MARK: - Properties 38 | 39 | /// The height of the line 40 | open var height: CGFloat = 1.0 { 41 | didSet { 42 | constraints.filter { $0.identifier == "height" }.forEach { $0.constant = height } // Assumes constraint was given an identifier 43 | invalidateIntrinsicContentSize() 44 | } 45 | } 46 | 47 | open override var intrinsicContentSize: CGSize { 48 | return CGSize(width: super.intrinsicContentSize.width, height: height) 49 | } 50 | 51 | // MARK: - Initialization 52 | 53 | public override init(frame: CGRect) { 54 | super.init(frame: frame) 55 | setup() 56 | } 57 | 58 | required public init?(coder aDecoder: NSCoder) { 59 | super.init(coder: aDecoder) 60 | setup() 61 | } 62 | 63 | /// Sets up the default properties 64 | open func setup() { 65 | backgroundColor = .lightGray 66 | translatesAutoresizingMaskIntoConstraints = false 67 | setContentHuggingPriority(.defaultHigh, for: .vertical) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Views/InputStackView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /** 28 | A UIStackView that's intended for holding `InputItem`s 29 | 30 | ## Important Notes ## 31 | 1. Default alignment is .fill 32 | 2. Default distribution is .fill 33 | 3. The distribution property needs to be based on its arranged subviews intrinsicContentSize so it is not recommended to change it 34 | */ 35 | open class InputStackView: UIStackView { 36 | 37 | /// The stack view position in the MessageInputBar 38 | /// 39 | /// - left: Left Stack View 40 | /// - right: Bottom Stack View 41 | /// - bottom: Left Stack View 42 | /// - top: Top Stack View 43 | public enum Position { 44 | case left, right, bottom, top 45 | } 46 | 47 | // MARK: Initialization 48 | 49 | public convenience init(axis: NSLayoutConstraint.Axis, spacing: CGFloat) { 50 | self.init(frame: .zero) 51 | self.axis = axis 52 | self.spacing = spacing 53 | } 54 | 55 | public override init(frame: CGRect) { 56 | super.init(frame: frame) 57 | setup() 58 | } 59 | 60 | required public init(coder: NSCoder) { 61 | super.init(coder: coder) 62 | } 63 | 64 | // MARK: - Setup 65 | 66 | /// Sets up the default properties 67 | open func setup() { 68 | translatesAutoresizingMaskIntoConstraints = false 69 | distribution = .fill 70 | alignment = .bottom 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Extensions/UITextView+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | internal extension UITextView { 28 | 29 | func find(prefixes: Set, with delimiterSet: CharacterSet) -> (prefix: String, word: String, range: NSRange)? { 30 | guard prefixes.count > 0, 31 | let result = wordAtCaret(with: delimiterSet), 32 | !result.word.isEmpty 33 | else { return nil } 34 | for prefix in prefixes { 35 | if result.word.hasPrefix(prefix) { 36 | return (prefix, result.word, result.range) 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | func wordAtCaret(with delimiterSet: CharacterSet) -> (word: String, range: NSRange)? { 43 | guard let caretRange = self.caretRange, 44 | let result = text.word(at: caretRange, with: delimiterSet) 45 | else { return nil } 46 | 47 | let location = result.range.lowerBound.encodedOffset 48 | let range = NSRange(location: location, length: result.range.upperBound.encodedOffset - location) 49 | 50 | return (result.word, range) 51 | } 52 | 53 | var caretRange: NSRange? { 54 | guard let selectedRange = self.selectedTextRange else { return nil } 55 | return NSRange( 56 | location: offset(from: beginningOfDocument, to: selectedRange.start), 57 | length: offset(from: selectedRange.start, to: selectedRange.end) 58 | ) 59 | } 60 | 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /CHANGELOG_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Changelog Guidelines 2 | 3 | Here you can find the general guidelines for maintaining the Changelog (or adding new entry). We follow the guidelines from [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) with few additions. 4 | 5 | ## Guiding Principles 6 | - Changelogs are for humans, not machines. 7 | - There should be an entry for every single version. 8 | - The same types of changes should be grouped. 9 | - Versions and sections should be linkable. 10 | - The latest version comes first. 11 | - The release date of each versions is displayed. 12 | - Mention whether you follow Semantic Versioning. 13 | 14 | ... with MessageKit-specific additions: 15 | - Keep an unreleased section at the top. 16 | - Add PR number and a GitHub tag at the end of each entry. 17 | - Each breaking change entry should have **Breaking Change** label at the beginning of this entry. 18 | - **Breaking Change** entries should be placed at the top of the section it's in. 19 | 20 | ## Types of changes 21 | - **Added** for new features. 22 | - **Changed** for changes in existing functionality. 23 | - **Deprecated** for soon-to-be removed features. 24 | - **Removed** for now removed features. 25 | - **Fixed** for any bug fixes. 26 | - **Security** in case of vulnerabilities. 27 | 28 | ## Example: 29 | 30 | ### Added 31 | 32 | - Added `removedCachedAttributes(for:MessageType)`, `removeAllCachedAttributes()`, and `attributesCacheMaxSize` to 33 | `MessagesCollectionViewFlowLayout` to manage the caching of layout information for messages. 34 | [#263](https://github.com/MessageKit/MessageKit/pull/263) by [@SD10](https://github.com/sd10). 35 | 36 | ### Changed 37 | 38 | - **Breaking Change** The properties `leftStackView`, `rightStackView` and `bottomStackView` in `MessageInputBar` are now of type `InputStackView`. The property `separatorLine` is also now of type `SeparatorLine` in `MessageInputBar`. 39 | [#273](https://github.com/MessageKit/MessageKit/pull/273) by [@nathantannar4](https://github.com/nathantannar4). 40 | 41 | - Layout information is now being cached by `MessagesCollectionViewFlowLayout` for each `MessageType` using the 42 | `messageId` property. (This means if your layout is dynamic over the `IndexPath` you need to handle cache invalidation). 43 | [#263](https://github.com/MessageKit/MessageKit/pull/263) by [@SD10](https://github.com/sd10). 44 | 45 | ### Fixed 46 | 47 | - Fixed a bug that prevented the `textAllignment` property of `InputTextView`'s `placeholderLabel` from having noticable differences when changed to `.center` or `.right`. 48 | [#262](https://github.com/MessageKit/MessageKit/pull/262) by [@nathantannar4](https://github.com/nathantannar4). 49 | 50 | ### Removed 51 | 52 | - **Breaking Change** Removed `additionalTopContentInset` property of `MessagesViewController` because this is no longer necessary 53 | when `extendedLayoutIncludesOpaqueBars` is `true`. 54 | [#250](https://github.com/MessageKit/MessageKit/pull/250) by [@SD10](https://github.com/SD10). -------------------------------------------------------------------------------- /Example/Example/Style Examples/FacebookInputBar.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | import MessageInputBar 27 | 28 | class FacebookInputBar: MessageInputBar { 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | configure() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | func configure() { 40 | backgroundView.backgroundColor = .white 41 | let button = InputBarButtonItem() 42 | button.setSize(CGSize(width: 36, height: 36), animated: false) 43 | button.setImage(#imageLiteral(resourceName: "ic_plus").withRenderingMode(.alwaysTemplate), for: .normal) 44 | button.imageView?.contentMode = .scaleAspectFit 45 | button.tintColor = UIColor(red: 0, green: 122/255, blue: 1, alpha: 1) 46 | inputTextView.backgroundColor = UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1) 47 | inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) 48 | inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) 49 | inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20) 50 | inputTextView.layer.borderColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1).cgColor 51 | inputTextView.layer.borderWidth = 1.0 52 | inputTextView.layer.cornerRadius = 16.0 53 | inputTextView.layer.masksToBounds = true 54 | inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) 55 | setLeftStackViewWidthConstant(to: 36, animated: false) 56 | setStackViewItems([button], forStack: .left, animated: false) 57 | sendButton.setSize(CGSize(width: 52, height: 36), animated: false) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Protocols/MessageInputBarDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | /// A protocol that can receive different event notifications from the MessageInputBar. 28 | public protocol MessageInputBarDelegate: AnyObject { 29 | 30 | /// Called when the default send button has been selected. 31 | /// 32 | /// - Parameters: 33 | /// - inputBar: The `MessageInputBar`. 34 | /// - text: The current text in the `InputTextView` of the `MessageInputBar`. 35 | func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) 36 | 37 | /// Called when the instrinsicContentSize of the MessageInputBar has changed. 38 | /// Can be used for adjusting content insets on other views to make sure 39 | /// the MessageInputBar does not cover up any other view. 40 | /// 41 | /// - Parameters: 42 | /// - inputBar: The `MessageInputBar`. 43 | /// - size: The new instrinsic content size. 44 | func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) 45 | 46 | /// Called when the `MessageInputBar`'s `InputTextView`'s text has changed. 47 | /// Useful for adding your own logic without the need of assigning a delegate or notification. 48 | /// 49 | /// - Parameters: 50 | /// - inputBar: The MessageInputBar 51 | /// - text: The current text in the MessageInputBar's InputTextView 52 | func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) 53 | } 54 | 55 | public extension MessageInputBarDelegate { 56 | 57 | func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {} 58 | 59 | func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) {} 60 | 61 | func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) {} 62 | } 63 | -------------------------------------------------------------------------------- /Plugins/AttachmentManager/Views/AttachmentCollectionView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | open class AttachmentCollectionView: UICollectionView { 28 | 29 | // MARK: - Properties 30 | 31 | open var intrinsicContentHeight: CGFloat = 100 { 32 | didSet { 33 | invalidateIntrinsicContentSize() 34 | } 35 | } 36 | 37 | open override var intrinsicContentSize: CGSize { 38 | return CGSize(width: 0, height: intrinsicContentHeight) 39 | } 40 | 41 | // MARK: - Initialization 42 | 43 | public init() { 44 | let layout = UICollectionViewFlowLayout() 45 | layout.scrollDirection = .horizontal 46 | layout.minimumLineSpacing = 0 47 | layout.sectionInset.top = 5 48 | layout.sectionInset.bottom = 5 49 | layout.headerReferenceSize = CGSize(width: 12, height: 0) 50 | layout.footerReferenceSize = CGSize(width: 12, height: 0) 51 | super.init(frame: .zero, collectionViewLayout: layout) 52 | setup() 53 | } 54 | 55 | public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { 56 | super.init(frame: frame, collectionViewLayout: layout) 57 | setup() 58 | } 59 | 60 | required public init?(coder aDecoder: NSCoder) { 61 | super.init(coder: aDecoder) 62 | setup() 63 | } 64 | 65 | // MARK: - Setup 66 | 67 | private func setup() { 68 | 69 | backgroundColor = .white 70 | alwaysBounceHorizontal = true 71 | showsHorizontalScrollIndicator = true 72 | setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .vertical) 73 | register(AttachmentCell.self, forCellWithReuseIdentifier: AttachmentCell.reuseIdentifier) 74 | register(ImageAttachmentCell.self, forCellWithReuseIdentifier: ImageAttachmentCell.reuseIdentifier) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | internal extension String { 28 | 29 | func wordParts(_ range: Range, _ delimiterSet: CharacterSet) -> (left: String.SubSequence, right: String.SubSequence)? { 30 | let leftView = self[.. (word: String, range: Range)? { 42 | guard !isEmpty, 43 | let range = Range(nsrange, in: self), 44 | let parts = self.wordParts(range, delimiterSet) 45 | else { return nil } 46 | 47 | // if the left-next character is in the delimiterSet, the "right word part" is the full word 48 | // short circuit with the right word part + its range 49 | if let characterBeforeRange = index(range.lowerBound, offsetBy: -1, limitedBy: startIndex), 50 | let character = self[characterBeforeRange].unicodeScalars.first, 51 | delimiterSet.contains(character) { 52 | let right = parts.right 53 | let word = String(right) 54 | return (word, right.startIndex ..< right.endIndex) 55 | } 56 | 57 | let joinedWord = String(parts.left + parts.right) 58 | guard !joinedWord.isEmpty else { return nil } 59 | return (joinedWord, parts.left.startIndex ..< parts.right.endIndex) 60 | } 61 | } 62 | 63 | extension Character { 64 | 65 | static var space: Character { 66 | return " " 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Example/Example/Style Examples/iMessageInputBar.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | import MessageInputBar 27 | 28 | class iMessageInputBar: MessageInputBar { 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | configure() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | func configure() { 40 | inputTextView.backgroundColor = UIColor(red: 250/255, green: 250/255, blue: 250/255, alpha: 1) 41 | inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) 42 | inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 36) 43 | inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 36) 44 | inputTextView.layer.borderColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1).cgColor 45 | inputTextView.layer.borderWidth = 1.0 46 | inputTextView.layer.cornerRadius = 16.0 47 | inputTextView.layer.masksToBounds = true 48 | inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) 49 | setRightStackViewWidthConstant(to: 38, animated: false) 50 | setStackViewItems([sendButton, InputBarButtonItem.fixedSpace(2)], forStack: .right, animated: false) 51 | sendButton.imageView?.backgroundColor = tintColor 52 | sendButton.contentEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) 53 | sendButton.setSize(CGSize(width: 36, height: 36), animated: false) 54 | sendButton.image = #imageLiteral(resourceName: "ic_up") 55 | sendButton.title = nil 56 | sendButton.imageView?.layer.cornerRadius = 16 57 | sendButton.backgroundColor = .clear 58 | textViewPadding.right = -38 59 | separatorLine.isHidden = true 60 | backgroundView.backgroundColor = .white 61 | isTranslucent = true 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Example/Example/TableViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | final class TableViewController: UITableViewController { 28 | 29 | let styles: [MessageInputBarStyle] = [.default, .imessage, .slack, .facebook, .githawk] 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | tableView.delegate = self 34 | tableView.dataSource = self 35 | tableView.tableFooterView = UIView() 36 | title = "MessageInputBar" 37 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "Styles", style: .plain, target: nil, action: nil) 38 | navigationController?.navigationBar.tintColor = .white 39 | navigationController?.navigationBar.barTintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1) 40 | navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.bold) ] 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 44 | return "Styles" 45 | } 46 | 47 | override func numberOfSections(in tableView: UITableView) -> Int { 48 | return 1 49 | } 50 | 51 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return styles.count 53 | } 54 | 55 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 56 | let cell = UITableViewCell() 57 | cell.textLabel?.text = styles[indexPath.row].rawValue 58 | cell.accessoryType = .disclosureIndicator 59 | return cell 60 | } 61 | 62 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 63 | navigationController?.pushViewController(ExampleViewController(messageInputBarStyle: styles[indexPath.row]), animated: true) 64 | // splitViewController?.showDetailViewController(ExampleViewController(), sender: self) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Protocols/AutocompleteManagerDataSource.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /// AutocompleteManagerDataSource is a protocol that passes data to the AutocompleteManager 28 | public protocol AutocompleteManagerDataSource: AnyObject { 29 | 30 | /// The autocomplete options for the registered prefix. 31 | /// 32 | /// - Parameters: 33 | /// - manager: The AutocompleteManager 34 | /// - prefix: The registered prefix 35 | /// - Returns: An array of `AutocompleteCompletion` options for the given prefix 36 | func autocompleteManager(_ manager: AutocompleteManager, autocompleteSourceFor prefix: String) -> [AutocompleteCompletion] 37 | 38 | /// The cell to populate the `AutocompleteTableView` with 39 | /// 40 | /// - Parameters: 41 | /// - manager: The `AttachmentManager` that sources the UITableViewDataSource 42 | /// - tableView: The `AttachmentManager`'s `AutocompleteTableView` 43 | /// - indexPath: The `IndexPath` of the cell 44 | /// - session: The current `Session` of the `AutocompleteManager` 45 | /// - Returns: A UITableViewCell to populate the `AutocompleteTableView` 46 | func autocompleteManager(_ manager: AutocompleteManager, tableView: UITableView, cellForRowAt indexPath: IndexPath, for session: AutocompleteSession) -> UITableViewCell 47 | } 48 | 49 | public extension AutocompleteManagerDataSource { 50 | 51 | func autocompleteManager(_ manager: AutocompleteManager, tableView: UITableView, cellForRowAt indexPath: IndexPath, for session: AutocompleteSession) -> UITableViewCell { 52 | 53 | guard let cell = tableView.dequeueReusableCell(withIdentifier: AutocompleteCell.reuseIdentifier, for: indexPath) as? AutocompleteCell else { 54 | fatalError("AutocompleteCell is not registered") 55 | } 56 | 57 | cell.textLabel?.attributedText = cell.attributedText(matching: session) 58 | cell.backgroundColor = .white 59 | cell.separatorLine.isHidden = tableView.numberOfRows(inSection: indexPath.section) - 1 == indexPath.row 60 | return cell 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Sources/Models/NSConstraintLayoutSet.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | internal class NSLayoutConstraintSet { 28 | 29 | internal var top: NSLayoutConstraint? 30 | internal var bottom: NSLayoutConstraint? 31 | internal var left: NSLayoutConstraint? 32 | internal var right: NSLayoutConstraint? 33 | internal var centerX: NSLayoutConstraint? 34 | internal var centerY: NSLayoutConstraint? 35 | internal var width: NSLayoutConstraint? 36 | internal var height: NSLayoutConstraint? 37 | 38 | internal init(top: NSLayoutConstraint? = nil, bottom: NSLayoutConstraint? = nil, 39 | left: NSLayoutConstraint? = nil, right: NSLayoutConstraint? = nil, 40 | centerX: NSLayoutConstraint? = nil, centerY: NSLayoutConstraint? = nil, 41 | width: NSLayoutConstraint? = nil, height: NSLayoutConstraint? = nil) { 42 | self.top = top 43 | self.bottom = bottom 44 | self.left = left 45 | self.right = right 46 | self.centerX = centerX 47 | self.centerY = centerY 48 | self.width = width 49 | self.height = height 50 | } 51 | 52 | /// All of the currently configured constraints 53 | private var availableConstraints: [NSLayoutConstraint] { 54 | let constraints = [top, bottom, left, right, centerX, centerY, width, height] 55 | var available: [NSLayoutConstraint] = [] 56 | for constraint in constraints { 57 | if let value = constraint { 58 | available.append(value) 59 | } 60 | } 61 | return available 62 | } 63 | 64 | /// Activates all of the non-nil constraints 65 | /// 66 | /// - Returns: Self 67 | @discardableResult 68 | internal func activate() -> Self { 69 | NSLayoutConstraint.activate(availableConstraints) 70 | return self 71 | } 72 | 73 | /// Deactivates all of the non-nil constraints 74 | /// 75 | /// - Returns: Self 76 | @discardableResult 77 | internal func deactivate() -> Self { 78 | NSLayoutConstraint.deactivate(availableConstraints) 79 | return self 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Extensions/NSMutableAttributedString+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | extension NSMutableAttributedString { 28 | 29 | @discardableResult 30 | internal func bold(_ text: String, fontSize: CGFloat = UIFont.preferredFont(forTextStyle: .body).pointSize, textColor: UIColor = .black) -> NSMutableAttributedString { 31 | let attrs: [NSAttributedString.Key:AnyObject] = [ 32 | NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: fontSize), 33 | NSAttributedString.Key.foregroundColor : textColor 34 | ] 35 | let boldString = NSMutableAttributedString(string: text, attributes: attrs) 36 | self.append(boldString) 37 | return self 38 | } 39 | 40 | @discardableResult 41 | internal func normal(_ text: String, fontSize: CGFloat = UIFont.preferredFont(forTextStyle: .body).pointSize, textColor: UIColor = .black) -> NSMutableAttributedString { 42 | let attrs:[NSAttributedString.Key:AnyObject] = [ 43 | NSAttributedString.Key.font : UIFont.systemFont(ofSize: fontSize), 44 | NSAttributedString.Key.foregroundColor : textColor 45 | ] 46 | let normal = NSMutableAttributedString(string: text, attributes: attrs) 47 | self.append(normal) 48 | return self 49 | } 50 | 51 | } 52 | 53 | extension NSAttributedString { 54 | 55 | internal func replacingCharacters(in range: NSRange, with attributedString: NSAttributedString) -> NSMutableAttributedString { 56 | let ns = NSMutableAttributedString(attributedString: self) 57 | ns.replaceCharacters(in: range, with: attributedString) 58 | return ns 59 | } 60 | 61 | internal static func += (lhs: inout NSAttributedString, rhs: NSAttributedString) { 62 | let ns = NSMutableAttributedString(attributedString: lhs) 63 | ns.append(rhs) 64 | lhs = ns 65 | } 66 | 67 | internal static func + (lhs: NSAttributedString, rhs: NSAttributedString) -> NSAttributedString { 68 | let ns = NSMutableAttributedString(attributedString: lhs) 69 | ns.append(rhs) 70 | return NSAttributedString(attributedString: ns) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Tests/InputTextViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import XCTest 26 | @testable import MessageInputBar 27 | 28 | class InputTextViewTests: XCTestCase { 29 | 30 | var textView: InputTextView! 31 | 32 | override func setUp() { 33 | super.setUp() 34 | textView = InputTextView() 35 | } 36 | 37 | override func tearDown() { 38 | textView = nil 39 | super.tearDown() 40 | } 41 | 42 | func testPlaceholderLabelSetup() { 43 | XCTAssertEqual(textView.placeholderLabel.numberOfLines, 0) 44 | XCTAssertEqual(textView.placeholderLabel.textColor, UIColor.lightGray) 45 | XCTAssertEqual(textView.placeholderLabel.text, "New Message") 46 | XCTAssertEqual(textView.placeholderLabel.backgroundColor, UIColor.clear) 47 | XCTAssertFalse(textView.placeholderLabel.translatesAutoresizingMaskIntoConstraints) 48 | } 49 | 50 | func testPlaceholderLabelIsHiddenWhenTextIsEmpty() { 51 | textView.text = "" 52 | XCTAssertFalse(textView.placeholderLabel.isHidden) 53 | } 54 | 55 | func testPlaceholderLabelIsNotHiddenWhenTextIsNotEmpty() { 56 | textView.text = "New Text" 57 | XCTAssert(textView.placeholderLabel.isHidden) 58 | } 59 | 60 | func testPlaceholderTextChanging() { 61 | textView.placeholder = "New Placeholder" 62 | XCTAssertEqual(textView.placeholderLabel.text, "New Placeholder") 63 | } 64 | 65 | func testPlaceholderTextColorChanging() { 66 | textView.placeholderTextColor = UIColor.red 67 | XCTAssertEqual(textView.placeholderLabel.textColor, UIColor.red) 68 | } 69 | 70 | func testFontChanging() { 71 | textView.font = UIFont.systemFont(ofSize: 14) 72 | XCTAssertEqual(textView.placeholderLabel.font, UIFont.systemFont(ofSize: 14)) 73 | } 74 | 75 | func testTextAlignmentChanging() { 76 | textView.textAlignment = .center 77 | XCTAssertEqual(textView.placeholderLabel.textAlignment, .center) 78 | } 79 | 80 | func testSetup() { 81 | textView.setup() 82 | XCTAssertEqual(textView.font, UIFont.preferredFont(forTextStyle: .body)) 83 | XCTAssertEqual(textView.textContainerInset, UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)) 84 | XCTAssertFalse(textView.isScrollEnabled) 85 | XCTAssertTrue(textView.subviews.contains(textView.placeholderLabel)) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | import UIKit 25 | 26 | @UIApplicationMain 27 | class AppDelegate: UIResponder, UIApplicationDelegate { 28 | 29 | var window: UIWindow? 30 | 31 | 32 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 33 | // Override point for customization after application launch. 34 | 35 | window = UIWindow() 36 | window?.backgroundColor = .white 37 | let splitViewController = SplitViewController() 38 | splitViewController.viewControllers = [UINavigationController(rootViewController: TableViewController())] 39 | window?.rootViewController = splitViewController 40 | window?.makeKeyAndVisible() 41 | 42 | return true 43 | } 44 | 45 | func applicationWillResignActive(_ application: UIApplication) { 46 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 47 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 48 | } 49 | 50 | func applicationDidEnterBackground(_ application: UIApplication) { 51 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 52 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 53 | } 54 | 55 | func applicationWillEnterForeground(_ application: UIApplication) { 56 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 57 | } 58 | 59 | func applicationDidBecomeActive(_ application: UIApplication) { 60 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 61 | } 62 | 63 | func applicationWillTerminate(_ application: UIApplication) { 64 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 65 | } 66 | 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | 7 | Swift 9 | 10 | 11 | CocoaPods 13 | 14 | 15 | Xcode 17 | 18 | 19 | MIT 21 | 22 | 23 | Contributions Welcome 25 | 26 | 27 | ## Installation 28 | 29 | ### [CocoaPods](https://cocoapods.org/) **Recommended** 30 | ````ruby 31 | pod 'MessageInputBar' 32 | ```` 33 | 34 | ### [Carthage](https://github.com/Carthage/Carthage) 35 | 36 | To integrate MessageKit using Carthage, add the following to your `Cartfile`: 37 | 38 | ```` 39 | github "MessageKit/MessageInputBar" 40 | ```` 41 | 42 | ## Requirements 43 | 44 | - **iOS 9** or later 45 | - **Swift 4** or later 46 | 47 | > Version 0.4.0 onwards is only Swift 4.2 compatible 48 | 49 | 50 | ## Contributing 51 | 52 | Great! Look over these things first. 53 | - Please read our [Code of Conduct](https://github.com/MessageKit/MessageInputBar/blob/master/Code_of_Conduct.md) 54 | - Check the [Contributing Guide Lines](https://github.com/MessageKit/MessageInputBar/blob/master/CONTRIBUTING.md). 55 | - Come join us on [Slack](https://join.slack.com/t/messagekit/shared_invite/MjI4NzIzNzMyMzU0LTE1MDMwODIzMDUtYzllYzIyNTU4MA) and 🗣 don't be a stranger. 56 | - Check out the [current issues](https://github.com/MessageKit/MessageInputBar/issues) and see if you can tackle any of those. 57 | - Download the project and check out the current code base. Suggest any improvements by opening a new issue. 58 | - Check out the [What's Next](#whats-next) section :point_down: to see where we are headed. 59 | - Check [StackOverflow](https://stackoverflow.com/questions/tagged/messagekit) 60 | - Install [SwiftLint](https://github.com/realm/SwiftLint) too keep yourself in :neckbeard: style. 61 | - Be kind and helpful. 62 | 63 | 64 | ## What's Next? 65 | 66 | Check out the [Releases](https://github.com/MessageKit/MessageInputBar/releases) to see what we are working on next. 67 | 68 | ## Contact 69 | 70 | Have a question or an issue about MessageKit? Create an [issue](https://github.com/MessageKit/MessageInputBar/issues/new)! 71 | 72 | Interested in contributing to MessageKit? Click here to join our [Slack](https://join.slack.com/t/messagekit/shared_invite/MjI4NzIzNzMyMzU0LTE1MDMwODIzMDUtYzllYzIyNTU4MA). 73 | 74 | ### Apps using this library 75 | 76 | Add your app to the list of apps using this library and make a pull request. 77 | 78 | *Please provide attribution, it is greatly appreciated.* 79 | 80 | ## MessageKit Core Team 81 | 82 | - [@SD10](https://github.com/sd10), Steven Deutsch 83 | - [@nathantannar4](https://github.com/nathantannar4), Nathan Tannar (MessageInputBar Maintainer) 84 | - [@zhongwuzw](https://github.com/zhongwuzw), Wu Zhong 85 | 86 | ## Thanks 87 | 88 | Many thanks to [**the contributors**](https://github.com/MessageKit/MessageInputBar/graphs/contributors) of this project. 89 | 90 | ## License 91 | MessageInputBar is released under the [MIT License](https://github.com/MessageKit/MessageInputBar/blob/master/LICENSE.md). 92 | -------------------------------------------------------------------------------- /Tests/InputBarItemTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import XCTest 26 | @testable import MessageInputBar 27 | 28 | class InputBarItemTests: XCTestCase { 29 | 30 | var button: InputBarButtonItem! 31 | 32 | override func setUp() { 33 | super.setUp() 34 | button = InputBarButtonItem() 35 | } 36 | 37 | override func tearDown() { 38 | button = nil 39 | super.tearDown() 40 | } 41 | 42 | func testSetup() { 43 | XCTAssertEqual(button.contentVerticalAlignment, .center) 44 | XCTAssertEqual(button.contentHorizontalAlignment, .center) 45 | XCTAssertEqual(button.imageView?.contentMode, .scaleAspectFit) 46 | XCTAssertEqual(button.titleColor(for: .normal), UIColor(red: 0, green: 122/255, blue: 1, alpha: 1)) 47 | XCTAssertEqual(button.titleColor(for: .highlighted), UIColor(red: 0, green: 122/255, blue: 1, alpha: 0.3)) 48 | XCTAssertEqual(button.titleColor(for: .disabled), UIColor.lightGray) 49 | XCTAssertFalse(button.adjustsImageWhenHighlighted) 50 | } 51 | 52 | func testImagePropertyDefaultNil() { 53 | XCTAssertNil(button.image) 54 | } 55 | 56 | func testImagePropertyGettersAndSetters() { 57 | button.image = UIImage() 58 | XCTAssertNotNil(button.image) 59 | XCTAssertEqual(button.image!.pngData(), UIImage().pngData()) 60 | } 61 | 62 | func testIsHighlightedProperty() { 63 | var onSelectedCalled = false 64 | 65 | button.onSelected { (_) in 66 | onSelectedCalled = true 67 | } 68 | 69 | button.isHighlighted = true 70 | 71 | XCTAssert(onSelectedCalled) 72 | } 73 | 74 | func testIsNotHighlightedProperty() { 75 | var onDeselectedCalled = false 76 | 77 | button.isHighlighted = true 78 | button.onDeselected { (_) in 79 | onDeselectedCalled = true 80 | } 81 | 82 | button.isHighlighted = false 83 | 84 | XCTAssert(onDeselectedCalled) 85 | } 86 | 87 | func testIsEnabledProperty() { 88 | var onEnabledCalled = false 89 | button.onEnabled { (_) in 90 | onEnabledCalled = true 91 | } 92 | button.isEnabled = true 93 | 94 | XCTAssert(onEnabledCalled) 95 | } 96 | 97 | func testIsNotEnabledProperty() { 98 | var onDisabledCalled = false 99 | button.onDisabled { (_) in 100 | onDisabledCalled = true 101 | } 102 | button.isEnabled = false 103 | 104 | XCTAssert(onDisabledCalled) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Plugins/AttachmentManager/Protocols/AttachmentManagerDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /// AttachmentManagerDelegate is a protocol that can recieve notifications from the AttachmentManager 28 | public protocol AttachmentManagerDelegate: AnyObject { 29 | 30 | /// Can be used to determine if the AttachmentManager should be inserted into an InputStackView 31 | /// 32 | /// - Parameters: 33 | /// - manager: The AttachmentManager 34 | /// - shouldBecomeVisible: If the AttachmentManager should be presented or dismissed 35 | func attachmentManager(_ manager: AttachmentManager, shouldBecomeVisible: Bool) 36 | 37 | 38 | /// Notifys when an attachment has been inserted into the AttachmentManager 39 | /// 40 | /// - Parameters: 41 | /// - manager: The AttachmentManager 42 | /// - attachment: The attachment that was inserted 43 | /// - index: The index of the attachment in the AttachmentManager's attachments array 44 | func attachmentManager(_ manager: AttachmentManager, didInsert attachment: AttachmentManager.Attachment, at index: Int) 45 | 46 | /// Notifys when an attachment has been removed from the AttachmentManager 47 | /// 48 | /// - Parameters: 49 | /// - manager: The AttachmentManager 50 | /// - attachment: The attachment that was removed 51 | /// - index: The index of the attachment in the AttachmentManager's attachments array 52 | func attachmentManager(_ manager: AttachmentManager, didRemove attachment: AttachmentManager.Attachment, at index: Int) 53 | 54 | /// Notifys when the AttachmentManager was reloaded 55 | /// 56 | /// - Parameters: 57 | /// - manager: The AttachmentManager 58 | /// - attachments: The AttachmentManager's attachments array 59 | func attachmentManager(_ manager: AttachmentManager, didReloadTo attachments: [AttachmentManager.Attachment]) 60 | 61 | /// Notifys when the AddAttachmentCell was selected 62 | /// 63 | /// - Parameters: 64 | /// - manager: The AttachmentManager 65 | /// - attachments: The index of the AddAttachmentCell 66 | func attachmentManager(_ manager: AttachmentManager, didSelectAddAttachmentAt index: Int) 67 | } 68 | 69 | public extension AttachmentManagerDelegate { 70 | 71 | func attachmentManager(_ manager: AttachmentManager, didInsert attachment: AttachmentManager.Attachment, at index: Int) {} 72 | 73 | func attachmentManager(_ manager: AttachmentManager, didRemove attachment: AttachmentManager.Attachment, at index: Int) {} 74 | 75 | func attachmentManager(_ manager: AttachmentManager, didReloadTo attachments: [AttachmentManager.Attachment]) {} 76 | 77 | func attachmentManager(_ manager: AttachmentManager, didSelectAddAttachmentAt index: Int) {} 78 | } 79 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Protocols/AutocompleteManagerDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /// AutocompleteManagerDelegate is a protocol that more precisely define AutocompleteManager logic 28 | public protocol AutocompleteManagerDelegate: AnyObject { 29 | 30 | /// Can be used to determine if the AutocompleteManager should be inserted into an InputStackView 31 | /// 32 | /// - Parameters: 33 | /// - manager: The AutocompleteManager 34 | /// - shouldBecomeVisible: If the AutocompleteManager should be presented or dismissed 35 | func autocompleteManager(_ manager: AutocompleteManager, shouldBecomeVisible: Bool) 36 | 37 | /// Determines if a prefix character should be registered to initialize the auto-complete selection table 38 | /// 39 | /// - Parameters: 40 | /// - manager: The AutocompleteManager 41 | /// - prefix: The prefix `Character` could be registered 42 | /// - range: The `NSRange` of the prefix in the UITextView managed by the AutocompleteManager 43 | /// - Returns: If the prefix should be registered. Default is TRUE 44 | func autocompleteManager(_ manager: AutocompleteManager, shouldRegister prefix: String, at range: NSRange) -> Bool 45 | 46 | /// Determines if a prefix character should be unregistered to de-initialize the auto-complete selection table 47 | /// 48 | /// - Parameters: 49 | /// - manager: The AutocompleteManager 50 | /// - prefix: The prefix character could be unregistered 51 | /// - range: The range of the prefix in the UITextView managed by the AutocompleteManager 52 | /// - Returns: If the prefix should be unregistered. Default is TRUE 53 | func autocompleteManager(_ manager: AutocompleteManager, shouldUnregister prefix: String) -> Bool 54 | 55 | /// Determines if a prefix character can should be autocompleted 56 | /// 57 | /// - Parameters: 58 | /// - manager: The AutocompleteManager 59 | /// - prefix: The prefix character that is currently registered 60 | /// - text: The text to autocomplete with 61 | /// - Returns: If the prefix can be autocompleted. Default is TRUE 62 | func autocompleteManager(_ manager: AutocompleteManager, shouldComplete prefix: String, with text: String) -> Bool 63 | } 64 | 65 | public extension AutocompleteManagerDelegate { 66 | 67 | func autocompleteManager(_ manager: AutocompleteManager, shouldRegister prefix: String, at range: NSRange) -> Bool { 68 | return true 69 | } 70 | 71 | func autocompleteManager(_ manager: AutocompleteManager, shouldUnregister prefix: String) -> Bool { 72 | return true 73 | } 74 | 75 | func autocompleteManager(_ manager: AutocompleteManager, shouldComplete prefix: String, with text: String) -> Bool { 76 | return true 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /Example/Example/Style Examples/GitHawkInputBar.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | import MessageInputBar 27 | 28 | class GitHawkInputBar: MessageInputBar { 29 | 30 | private let githawkImages: [UIImage] = [#imageLiteral(resourceName: "ic_eye"), #imageLiteral(resourceName: "ic_bold"), #imageLiteral(resourceName: "ic_italic"), #imageLiteral(resourceName: "ic_at"), #imageLiteral(resourceName: "ic_list"), #imageLiteral(resourceName: "ic_code"), #imageLiteral(resourceName: "ic_link"), #imageLiteral(resourceName: "ic_hashtag"), #imageLiteral(resourceName: "ic_upload")] 31 | 32 | override init(frame: CGRect) { 33 | super.init(frame: frame) 34 | configure() 35 | } 36 | 37 | required init?(coder aDecoder: NSCoder) { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | 41 | func configure() { 42 | backgroundView.backgroundColor = .white 43 | inputTextView.backgroundColor = .clear 44 | inputTextView.layer.borderWidth = 0 45 | inputTextView.placeholder = "Leave a comment" 46 | sendButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) 47 | sendButton.setSize(CGSize(width: 36, height: 36), animated: false) 48 | sendButton.image = #imageLiteral(resourceName: "ic_send").withRenderingMode(.alwaysTemplate) 49 | sendButton.title = nil 50 | sendButton.tintColor = tintColor 51 | 52 | let layout = UICollectionViewFlowLayout() 53 | layout.scrollDirection = .horizontal 54 | layout.itemSize = CGSize(width: 20, height: 20) 55 | layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 20) 56 | let collectionView = AttachmentCollectionView(frame: .zero, collectionViewLayout: layout) 57 | collectionView.intrinsicContentHeight = 20 58 | collectionView.dataSource = self 59 | collectionView.showsHorizontalScrollIndicator = false 60 | collectionView.register(ImageCell.self, forCellWithReuseIdentifier: ImageCell.reuseIdentifier) 61 | bottomStackView.addArrangedSubview(collectionView) 62 | collectionView.reloadData() 63 | } 64 | 65 | } 66 | 67 | extension GitHawkInputBar: UICollectionViewDataSource { 68 | 69 | func numberOfSections(in collectionView: UICollectionView) -> Int { 70 | return githawkImages.count 71 | } 72 | 73 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 74 | return 1 75 | } 76 | 77 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 78 | 79 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCell.reuseIdentifier, for: indexPath) as! ImageCell 80 | cell.imageView.image = githawkImages[indexPath.section].withRenderingMode(.alwaysTemplate) 81 | cell.imageView.tintColor = .black 82 | return cell 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /MessageInputBar.xcodeproj/xcshareddata/xcschemes/MessageInputBar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Tests/MessageInputBarTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import XCTest 26 | @testable import MessageInputBar 27 | 28 | class MessageInputBarTests: XCTestCase { 29 | 30 | var sut: MessageInputBar! 31 | 32 | override func setUp() { 33 | super.setUp() 34 | sut = MessageInputBar() 35 | } 36 | 37 | override func tearDown() { 38 | sut = nil 39 | super.tearDown() 40 | } 41 | 42 | func testBlurEffectTranslatesAutoresizingMaskIntoConstraints_isFalseAfterInit() { 43 | XCTAssertFalse(sut.blurView.translatesAutoresizingMaskIntoConstraints) 44 | } 45 | 46 | func testIsTranslucent_isFalseForDefault() { 47 | XCTAssertFalse(sut.isTranslucent) 48 | } 49 | 50 | func testUISetups_forIsTranslucentIsTrue() { 51 | sut.isTranslucent = true 52 | XCTAssertFalse(sut.blurView.isHidden) 53 | } 54 | 55 | func testUISetups_forIsTranslucentIsFalse() { 56 | sut.isTranslucent = false 57 | XCTAssertTrue(sut.blurView.isHidden) 58 | } 59 | 60 | func testSeparatorLine_isNotNilAfterInit() { 61 | XCTAssertNotNil(sut.separatorLine) 62 | } 63 | 64 | func testLeftStackView_isNotNilAfterInit() { 65 | XCTAssertNotNil(sut.leftStackView) 66 | } 67 | 68 | func testLeftStackViewAxis_isHorizontalAfterInit() { 69 | XCTAssertEqual(sut.leftStackView.axis, .horizontal) 70 | } 71 | 72 | func testLeftStackViewSpacing_isZeroAfterInit() { 73 | XCTAssertEqual(sut.leftStackView.spacing, 0) 74 | } 75 | 76 | func testRightStackView_isNotNilAfterInit() { 77 | XCTAssertNotNil(sut.rightStackView) 78 | } 79 | 80 | func testRightStackViewAxis_isHorizontalAfterInit() { 81 | XCTAssertEqual(sut.rightStackView.axis, .horizontal) 82 | } 83 | 84 | func testRightStackViewSpacing_isZeroAfterInit() { 85 | XCTAssertEqual(sut.rightStackView.spacing, 0) 86 | } 87 | 88 | func testBottomStackView_isNotNilAfterInit() { 89 | XCTAssertNotNil(sut.bottomStackView) 90 | } 91 | 92 | func testBottomStackViewAxis_isHorizontalAfterInit() { 93 | XCTAssertEqual(sut.bottomStackView.axis, .horizontal) 94 | } 95 | 96 | func testBottomStackViewSpacing_isZeroAfterInit() { 97 | XCTAssertEqual(sut.bottomStackView.spacing, 15) 98 | } 99 | 100 | func testInputTextViewMessageInputBar_isSelf() { 101 | XCTAssertEqual(sut.inputTextView.messageInputBar, sut) 102 | } 103 | 104 | func testInputTextViewTranslatesAutoresizingMaskIntoConstraints_isFalseAfterInit() { 105 | XCTAssertFalse(sut.inputTextView.translatesAutoresizingMaskIntoConstraints) 106 | } 107 | 108 | func testSendButtonTitle_isSendAfterInit() { 109 | XCTAssertEqual(sut.sendButton.title, "Send") 110 | } 111 | 112 | func testSendButtonIsEnabled_isFalseAfterInit() { 113 | XCTAssertFalse(sut.sendButton.isEnabled) 114 | } 115 | 116 | func testSendButtonFont_isHeadlineAfterInit() { 117 | XCTAssertEqual(sut.sendButton.titleLabel?.font, UIFont.systemFont(ofSize: 15, weight: .bold)) 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /Plugins/AutocompleteManager/Views/AutocompleteCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | open class AutocompleteCell: UITableViewCell { 28 | 29 | // MARK: - Properties 30 | 31 | open class var reuseIdentifier: String { 32 | return "AutocompleteCell" 33 | } 34 | 35 | /// A boarder line anchored to the top of the view 36 | public let separatorLine = SeparatorLine() 37 | 38 | open var imageViewEdgeInsets: UIEdgeInsets = .zero 39 | 40 | // MARK: - Initialization 41 | 42 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 43 | super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) 44 | setup() 45 | } 46 | 47 | required public init?(coder aDecoder: NSCoder) { 48 | super.init(coder: aDecoder) 49 | setup() 50 | } 51 | 52 | open override func prepareForReuse() { 53 | super.prepareForReuse() 54 | textLabel?.text = nil 55 | detailTextLabel?.text = nil 56 | imageView?.image = nil 57 | imageViewEdgeInsets = .zero 58 | separatorLine.backgroundColor = .lightGray 59 | separatorLine.isHidden = false 60 | } 61 | 62 | // MARK: - Setup 63 | 64 | private func setup() { 65 | 66 | setupSubviews() 67 | setupConstraints() 68 | } 69 | 70 | open func setupSubviews() { 71 | 72 | addSubview(separatorLine) 73 | } 74 | 75 | open func setupConstraints() { 76 | 77 | separatorLine.addConstraints(left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, heightConstant: separatorLine.height) 78 | } 79 | 80 | open override func layoutSubviews() { 81 | super.layoutSubviews() 82 | guard let imageViewFrame = imageView?.frame else { return } 83 | let imageViewOrigin = CGPoint(x: imageViewFrame.origin.x + imageViewEdgeInsets.left, y: imageViewFrame.origin.y + imageViewEdgeInsets.top) 84 | let imageViewSize = CGSize(width: imageViewFrame.size.width - imageViewEdgeInsets.left - imageViewEdgeInsets.right, height: imageViewFrame.size.height - imageViewEdgeInsets.top - imageViewEdgeInsets.bottom) 85 | imageView?.frame = CGRect(origin: imageViewOrigin, size: imageViewSize) 86 | } 87 | 88 | // MARK: - API [Public] 89 | 90 | open func attributedText(matching session: AutocompleteSession) -> NSMutableAttributedString { 91 | 92 | let completionText = (session.completion?.text ?? session.completion?.text) ?? "" 93 | 94 | // Bolds the text that currently matches the filter 95 | let matchingRange = (completionText as NSString).range(of: session.filter, options: .caseInsensitive) 96 | let attributedString = NSMutableAttributedString().normal(completionText) 97 | attributedString.addAttributes([.font: UIFont.boldSystemFont(ofSize: UIFont.preferredFont(forTextStyle: .body).pointSize)], range: matchingRange) 98 | let stringWithPrefix = NSMutableAttributedString().normal(String(session.prefix)) 99 | stringWithPrefix.append(attributedString) 100 | return stringWithPrefix 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ### Code of Conduct 4 | 5 | Please read our [Code of Conduct](https://github.com/MessageKit/MessageKit/blob/master/Code_of_Conduct.md). 6 | The MessageKit maintainers take this Code of Conduct very seriously. Intolerance, disrespect, harassment, and any form of negativity will not be tolerated. 7 | 8 | ### Ways to Contribute 9 | 10 | You can contribute to MessageKit in a variety of ways: 11 | 12 | - Fixing or reporting bugs :scream: 13 | - Improving documentation :heart: 14 | - Suggesting new features :smiley: 15 | - Increasing unit test coverage :pray: 16 | - Resolving any open issues :+1: 17 | 18 | If you're new to Open Source or Swift, the MessageKit community is a great place to get involved. 19 | 20 | **Your contributions are always welcome, no contribution is too small.** 21 | 22 | ### Opening a New Issue 23 | 24 | - Please check the [README](https://github.com/MessageKit/MessageKit/blob/master/README.md) to see if your question is answered there. 25 | - Search [open issues](https://github.com/MessageKit/MessageKit/issues?q=is%3Aopen+is%3Aissue) and [closed issues](https://github.com/MessageKit/MessageKit/issues?q=is%3Aissue+is%3Aclosed) to avoid opening a duplicate issue. 26 | - Avoiding duplicate issues organizes all relevant information for project maintainers and other users. 27 | - If no issues represent your problem, please open a new issue with a good title and useful description. 28 | 29 | - Information to Provide When Opening an Issue: 30 | - MessageKit version(s) 31 | - iOS version(s) 32 | - Devices/Simulators affected 33 | - Full crash log, if applicable 34 | - A well written description of the problem you are experiencing 35 | - *Please provide complete steps to reproduce the issue* 36 | - For UI related issues, please provide a screenshot/GIF/video showing the issue 37 | - Link to a project or demo project that exhibits the issue 38 | - Search for a list any issues that might be related 39 | 40 | The more information you can provide, the easier it will be for us to resolve your issue in a timely manner. 41 | 42 | ### Submitting a Pull Request 43 | 44 | We maintain two permanent, protected branches: `master` and `development`. 45 | 46 | `master` is for working on the current release, so any bug fixes or documentation spelling fixes should be merged into this branch. 47 | 48 | `development` is where we stage work for the *next* release, i.e. breaking API changes and related documentation updates. Contributors should gently encourage new pull-requests to point to the appropriate branch, and to rebase onto that branch if necessary. 49 | 50 | When a new version is ready to be released, please create a pull request to merge `development` into `master`, named something like "Release 10.0". Then we can have some final discussion before we merge it into `master` and push the release out to the public. 51 | 52 | Since `development` is a *shared* branch, it is important not to ever rebase this branch onto `master`. If a bug fix is applied to `master` it can be merged into `development` using good old simple `git checkout development && git merge master`. Yes this will clutter the history a little bit, but it also provides important context to know how/when a patch was applied. Merge commits can be considered necessary historical data, not warts on an idealized history graph. 53 | 54 | **You should submit one pull request per feature, the smaller the pull request the better chances it will be merged.** 55 | Enormous pull requests take a significant time to review and understand their implications on the existing codebase. 56 | 57 | ### Style Guidelines 58 | 59 | Writing clean code and upholding project standards is as important as adding new features. To ensure this, MessageKit employs a few practices: 60 | 61 | 1. We use [SwiftLint](https://github.com/realm/SwiftLint) to enforce style and conventions at compile time. 62 | 2. We adhere to the [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/). 63 | 3. We follow the [Raywenderlich Swift Style Guide ](https://github.com/raywenderlich/swift-style-guide) but may have slight variations. 64 | 65 | ### Questions and Contact 66 | 67 | If any of the sections above are unclear and require further explanation, *do not hesitate to reach out*. 68 | MessageKit strives to build an inclusive open source community and to make contributing as easy as possible for members of all experience levels. 69 | 70 | **You can get in touch with the MessageKit core team directly by joining our open Slack community channel: [here](https://join.slack.com/t/messagekit/shared_invite/MjI0NDkxNjgwMzA3LTE1MDIzMTU0MjUtMzJhZDZlNTkxMA).** 71 | 72 | ---- 73 | 74 | ## [No Brown M&M's](http://en.wikipedia.org/wiki/Van_Halen#Contract_riders) 75 | 76 | If you made it all the way to the end, bravo dear user, we love you. You can include this emoji in the top of your ticket to signal to us that you did in fact read this file and are trying to conform to it as best as possible: :ghost: 77 | -------------------------------------------------------------------------------- /Sources/Extensions/UIView+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | extension UIView { 28 | 29 | internal func fillSuperview() { 30 | guard let superview = self.superview else { 31 | return 32 | } 33 | translatesAutoresizingMaskIntoConstraints = false 34 | 35 | let constraints: [NSLayoutConstraint] = [ 36 | leftAnchor.constraint(equalTo: superview.leftAnchor), 37 | rightAnchor.constraint(equalTo: superview.rightAnchor), 38 | topAnchor.constraint(equalTo: superview.topAnchor), 39 | bottomAnchor.constraint(equalTo: superview.bottomAnchor) 40 | ] 41 | NSLayoutConstraint.activate(constraints) 42 | } 43 | 44 | internal func centerInSuperview() { 45 | guard let superview = self.superview else { 46 | return 47 | } 48 | translatesAutoresizingMaskIntoConstraints = false 49 | let constraints: [NSLayoutConstraint] = [ 50 | centerXAnchor.constraint(equalTo: superview.centerXAnchor), 51 | centerYAnchor.constraint(equalTo: superview.centerYAnchor) 52 | ] 53 | NSLayoutConstraint.activate(constraints) 54 | } 55 | 56 | internal func constraint(equalTo size: CGSize) { 57 | guard superview != nil else { return } 58 | translatesAutoresizingMaskIntoConstraints = false 59 | let constraints: [NSLayoutConstraint] = [ 60 | widthAnchor.constraint(equalToConstant: size.width), 61 | heightAnchor.constraint(equalToConstant: size.height) 62 | ] 63 | NSLayoutConstraint.activate(constraints) 64 | 65 | } 66 | 67 | @discardableResult 68 | internal func addConstraints(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] { 69 | 70 | if self.superview == nil { 71 | return [] 72 | } 73 | translatesAutoresizingMaskIntoConstraints = false 74 | 75 | var constraints = [NSLayoutConstraint]() 76 | 77 | if let top = top { 78 | let constraint = topAnchor.constraint(equalTo: top, constant: topConstant) 79 | constraint.identifier = "top" 80 | constraints.append(constraint) 81 | } 82 | 83 | if let left = left { 84 | let constraint = leftAnchor.constraint(equalTo: left, constant: leftConstant) 85 | constraint.identifier = "left" 86 | constraints.append(constraint) 87 | } 88 | 89 | if let bottom = bottom { 90 | let constraint = bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant) 91 | constraint.identifier = "bottom" 92 | constraints.append(constraint) 93 | } 94 | 95 | if let right = right { 96 | let constraint = rightAnchor.constraint(equalTo: right, constant: -rightConstant) 97 | constraint.identifier = "right" 98 | constraints.append(constraint) 99 | } 100 | 101 | if widthConstant > 0 { 102 | let constraint = widthAnchor.constraint(equalToConstant: widthConstant) 103 | constraint.identifier = "width" 104 | constraints.append(constraint) 105 | } 106 | 107 | if heightConstant > 0 { 108 | let constraint = heightAnchor.constraint(equalToConstant: heightConstant) 109 | constraint.identifier = "height" 110 | constraints.append(constraint) 111 | } 112 | 113 | NSLayoutConstraint.activate(constraints) 114 | return constraints 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Plugins/AttachmentManager/Views/AttachmentCell.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | open class AttachmentCell: UICollectionViewCell { 28 | 29 | // MARK: - Properties 30 | 31 | open class var reuseIdentifier: String { 32 | return "AttachmentCell" 33 | } 34 | 35 | public let containerView: UIView = { 36 | let view = UIView() 37 | view.translatesAutoresizingMaskIntoConstraints = false 38 | view.backgroundColor = .groupTableViewBackground 39 | view.layer.cornerRadius = 8 40 | view.clipsToBounds = true 41 | return view 42 | }() 43 | 44 | open var padding: UIEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) { 45 | didSet { 46 | updateContainerPadding() 47 | } 48 | } 49 | 50 | open lazy var deleteButton: UIButton = { [weak self] in 51 | let button = UIButton() 52 | button.setAttributedTitle(NSMutableAttributedString().bold("X", fontSize: 15, textColor: .white), for: .normal) 53 | button.setAttributedTitle(NSMutableAttributedString().bold("X", fontSize: 15, textColor: UIColor.white.withAlphaComponent(0.5)), for: .highlighted) 54 | button.layer.cornerRadius = 10 55 | button.clipsToBounds = true 56 | button.backgroundColor = UIColor(red: 0, green: 122/255, blue: 1, alpha: 1) 57 | button.addTarget(self, action: #selector(deleteAttachment), for: .touchUpInside) 58 | return button 59 | }() 60 | 61 | open var attachment: AttachmentManager.Attachment? 62 | 63 | open var indexPath: IndexPath? 64 | 65 | open weak var manager: AttachmentManager? 66 | 67 | private var containerViewLayoutSet: NSLayoutConstraintSet? 68 | 69 | // MARK: - Initialization 70 | 71 | public override init(frame: CGRect) { 72 | super.init(frame: frame) 73 | setup() 74 | } 75 | 76 | required public init?(coder aDecoder: NSCoder) { 77 | super.init(coder: aDecoder) 78 | setup() 79 | } 80 | 81 | open override func prepareForReuse() { 82 | super.prepareForReuse() 83 | indexPath = nil 84 | manager = nil 85 | attachment = nil 86 | } 87 | 88 | // MARK: - Setup 89 | 90 | private func setup() { 91 | 92 | setupSubviews() 93 | setupConstraints() 94 | } 95 | 96 | private func setupSubviews() { 97 | 98 | contentView.addSubview(containerView) 99 | contentView.addSubview(deleteButton) 100 | } 101 | 102 | private func setupConstraints() { 103 | 104 | containerViewLayoutSet = NSLayoutConstraintSet( 105 | top: containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: padding.top), 106 | bottom: containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -padding.bottom), 107 | left: containerView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: padding.left), 108 | right: containerView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -padding.right) 109 | ).activate() 110 | deleteButton.addConstraints(contentView.topAnchor, right: contentView.rightAnchor, widthConstant: 20, heightConstant: 20) 111 | } 112 | 113 | private func updateContainerPadding() { 114 | 115 | containerViewLayoutSet?.top?.constant = padding.top 116 | containerViewLayoutSet?.bottom?.constant = -padding.bottom 117 | containerViewLayoutSet?.left?.constant = padding.left 118 | containerViewLayoutSet?.right?.constant = -padding.right 119 | } 120 | 121 | // MARK: - User Actions 122 | 123 | @objc 124 | func deleteAttachment() { 125 | 126 | guard let index = indexPath?.row else { return } 127 | manager?.removeAttachment(at: index) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Documentation/MessageInputBar.md: -------------------------------------------------------------------------------- 1 | # MessageInputBar 2 | 3 | The `MessageInputBar` is the reactive `InputAccessoryView` of the `MessagesViewController`. It has a fairly basic delegate, `MessageInputBarDelegate`, that can be used to detect when the send button is pressed however its individual `InputBarButtonItem`'s can be individually set up to react to various events. 4 | 5 | ## Basic Setup 6 | 7 | We recommend viewing the example project for demos on how to make the `MessageInputBar` look like Facebook Messenger's, Slack's or iMessage. 8 | 9 | ## FAQ 10 | 11 | **Why doesn't the `MessageInputBar` appear in my controller?** 12 | 13 | If you're using the `MessagesViewController` as a child view controller then 14 | you have to call `becomeFirstResponder()` on your view controller. The 15 | reason is because the `MessageInputBar` is the `inputAccessoryView` of the 16 | `MessagesViewController`. This means that it is not in the view hierarchy of 17 | `MessagesViewController`'s root view. 18 | 19 | ```Swift 20 | class ParentVC: UIViewController { 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | let childVC = MessagesViewController() 24 | addChildViewController(childVC) 25 | self.view.addSubview(childVC.view) 26 | childVC.didMove(toParentViewController:self) 27 | self.becomeFirstResponder() 28 | } 29 | } 30 | ``` 31 | 32 | ```swift 33 | Q: HELP! All kinds of `UILayoutConstraint`'s are being broken? 34 | // A: Be sure that you are not animating any of the sizing during `viewDidLoad()`. For anything else, try https://www.wtfautolayout.com first 35 | 36 | Q: I set the items to the left stack view but I cannot see them? 37 | // A: Remeber to call `func setLeftStackViewWidthConstant(to newValue: CGFloat, animated: Bool)`. A width constraint is required so that the `InputTextView` always spans the width between the left and right stack views 38 | 39 | Q: How do I add padding? 40 | // A: There are two `UIEdgeInsets` that manage the padding. There is `padding` and 'textViewPadding`. Note that `textViewPadding.top` does nothing, as if you want to add padding between the top of the `InputTextView` and its superview you would use `padding`/ 41 | 42 | ``` 43 | 44 | ## Delegate 45 | 46 | ```swift 47 | func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) 48 | 49 | func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) 50 | 51 | func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) 52 | ``` 53 | 54 | ## Layout 55 | 56 | The layout of the `MessageInputBar` is made of of 3 `UIStackView`'s and an `InputTextView` (subclass of `UITextView`). The padding of the subviews can be easily adjusted by changing the `padding` and `textViewPadding` properties. The constraints will automatically be updated. 57 | 58 | 59 | 60 | ```swift 61 | H:|-(padding.left)-[UIStackView(leftStackViewWidthContant)]-(textViewPadding.left)-[InputTextView]-(textViewPadding.right)-[UIStackView(rightStackViewWidthContant)]-(padding.right)-| 62 | 63 | V:|-(padding.top)-[InputTextView]-(textViewPadding.bottom)-[UIStackView]-(padding.bottom)-| 64 | ``` 65 | 66 | It is important to note that each of the `UIStackView`'s to the left and right of the `InputTextView` are anchored by a width constraint. This way the `InputTextView` will always fill the space inbetween in addition to providing methods that can easily be called to hide all buttons to the right or left of the `InputTextView` by setting the width constraint constant to 0. 67 | 68 | ```swift 69 | func setLeftStackViewWidthConstant(to newValue: CGFloat, animated: Bool) 70 | func setRightStackViewWidthConstant(to newValue: CGFloat, animated: Bool) 71 | ``` 72 | 73 | ## InputBarButtonItem 74 | 75 | It is recommended that you use the `InputBarButtonItem` for the `UIStackView`'s. This is because all `UIStackView`'s are intitially set with the following properties: 76 | 77 | ```swift 78 | let view = UIStackView() 79 | view.axis = .horizontal 80 | view.distribution = .fill 81 | view.alignment = .fill 82 | view.spacing = 15 83 | ``` 84 | 85 | This will layout the arrangedViews based on their intrinsicContentSize and if there is extra space the views will be expanded based on their content hugging `UILayoutPriority`. 86 | 87 | ### Size 88 | 89 | Each `InputBarButtonItem`'s `intrinsicContentSize` can be overridden by setting the `size` property. It is optional so when set to `nil` the `super.intrinsicContentSize` will be used. 90 | 91 | ### Spacing 92 | 93 | Spacing can be set using the `spacing` property. This will change the content hugging `UILayoutPriority` and add extra space to the `intrinsicContentSize` when set to `.fixed(CGFloat)`. 94 | 95 | ### Hooks 96 | 97 | Each `InputBarButtonItem` has properties that can hold actions that will be executed during various hooks such as the button being touched, the `InputTextVIew `text changing and more! Thanks to these easy hooks with a few lines of code the items can be easily resized and animated similar to that of the Facebook messenger app. 98 | 99 | ```swift 100 | // MARK: - Hooks 101 | 102 | public typealias InputBarButtonItemAction = ((InputBarButtonItem) -> Void) 103 | 104 | private var onTouchUpInsideAction: InputBarButtonItemAction? 105 | private var onKeyboardEditingBeginsAction: InputBarButtonItemAction? 106 | private var onKeyboardEditingEndsAction: InputBarButtonItemAction? 107 | private var onTextViewDidChangeAction: ((InputBarButtonItem, InputTextView) -> Void)? 108 | private var onSelectedAction: InputBarButtonItemAction? 109 | private var onDeselectedAction: InputBarButtonItemAction? 110 | private var onEnabledAction: InputBarButtonItemAction? 111 | private var onDisabledAction: InputBarButtonItemAction? 112 | ``` 113 | 114 | ## Further Questions? 115 | 116 | Contact the components author [Nathan Tannar](https://github.com/nathantannar4) on the Slack channel. 117 | 118 | -------------------------------------------------------------------------------- /Example/Example/Style Examples/SlackInputBar.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | import MessageInputBar 27 | 28 | class SlackInputBar: MessageInputBar { 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | configure() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | func configure() { 40 | backgroundView.backgroundColor = .white 41 | inputTextView.backgroundColor = .clear 42 | inputTextView.layer.borderWidth = 0 43 | let items = [ 44 | makeButton(named: "ic_camera").onTextViewDidChange { button, textView in 45 | button.isEnabled = textView.text.isEmpty 46 | }.onSelected { 47 | $0.tintColor = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0) 48 | }, 49 | makeButton(named: "ic_at").onSelected { 50 | self.plugins.forEach { _ = $0.handleInput(of: "@" as AnyObject) } 51 | $0.tintColor = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0) 52 | }, 53 | makeButton(named: "ic_hashtag").onSelected { 54 | self.plugins.forEach { _ = $0.handleInput(of: "#" as AnyObject) } 55 | $0.tintColor = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0) 56 | }, 57 | .flexibleSpace, 58 | makeButton(named: "ic_library") 59 | .onSelected { 60 | $0.tintColor = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0) 61 | let imagePicker = UIImagePickerController() 62 | imagePicker.delegate = self 63 | imagePicker.sourceType = .photoLibrary 64 | (UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController?.present(imagePicker, animated: true, completion: nil) 65 | }, 66 | sendButton 67 | .configure { 68 | $0.layer.cornerRadius = 8 69 | $0.layer.borderWidth = 1.5 70 | $0.layer.borderColor = $0.titleColor(for: .disabled)?.cgColor 71 | $0.setTitleColor(.white, for: .normal) 72 | $0.setTitleColor(.white, for: .highlighted) 73 | $0.setSize(CGSize(width: 52, height: 30), animated: false) 74 | }.onDisabled { 75 | $0.layer.borderColor = $0.titleColor(for: .disabled)?.cgColor 76 | $0.backgroundColor = .white 77 | }.onEnabled { 78 | $0.backgroundColor = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0) 79 | $0.layer.borderColor = UIColor.clear.cgColor 80 | }.onSelected { 81 | // We use a transform becuase changing the size would cause the other views to relayout 82 | $0.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) 83 | }.onDeselected { 84 | $0.transform = CGAffineTransform.identity 85 | } 86 | ] 87 | items.forEach { $0.tintColor = .lightGray } 88 | 89 | // We can change the container insets if we want 90 | inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) 91 | inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5) 92 | 93 | let maxSizeItem = InputBarButtonItem() 94 | .configure { 95 | $0.image = UIImage(named: "icons8-expand")?.withRenderingMode(.alwaysTemplate) 96 | $0.tintColor = .darkGray 97 | $0.setSize(CGSize(width: 20, height: 20), animated: false) 98 | }.onSelected { 99 | let oldValue = $0.messageInputBar?.shouldForceTextViewMaxHeight ?? false 100 | $0.image = oldValue ? UIImage(named: "icons8-expand")?.withRenderingMode(.alwaysTemplate) : UIImage(named: "icons8-collapse")?.withRenderingMode(.alwaysTemplate) 101 | self.setShouldForceMaxTextViewHeight(to: !oldValue, animated: true) 102 | } 103 | rightStackView.alignment = .top 104 | setStackViewItems([maxSizeItem], forStack: .right, animated: false) 105 | setRightStackViewWidthConstant(to: 20, animated: false) 106 | 107 | // Finally set the items 108 | setStackViewItems(items, forStack: .bottom, animated: false) 109 | } 110 | 111 | 112 | 113 | private func makeButton(named: String) -> InputBarButtonItem { 114 | return InputBarButtonItem() 115 | .configure { 116 | $0.spacing = .fixed(10) 117 | $0.image = UIImage(named: named)?.withRenderingMode(.alwaysTemplate) 118 | $0.setSize(CGSize(width: 30, height: 30), animated: false) 119 | }.onSelected { 120 | $0.tintColor = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0) 121 | }.onDeselected { 122 | $0.tintColor = UIColor.lightGray 123 | }.onTouchUpInside { _ in 124 | print("Item Tapped") 125 | } 126 | } 127 | 128 | } 129 | 130 | extension SlackInputBar: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 131 | 132 | private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 133 | 134 | picker.dismiss(animated: true, completion: { 135 | if let pickedImage = info[UIImagePickerController.InfoKey.originalImage.rawValue] as? UIImage { 136 | self.plugins.forEach { _ = $0.handleInput(of: pickedImage) } 137 | } 138 | }) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Example/Example/ExampleViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | import MessageInputBar 27 | 28 | enum MessageInputBarStyle: String { 29 | case imessage = "iMessage" 30 | case slack = "Slack" 31 | case githawk = "GitHawk" 32 | case facebook = "Facebook" 33 | case `default` = "Default" 34 | 35 | func generate() -> MessageInputBar { 36 | switch self { 37 | case .imessage: return iMessageInputBar() 38 | case .slack: return SlackInputBar() 39 | case .githawk: return GitHawkInputBar() 40 | case .facebook: return FacebookInputBar() 41 | case .default: return MessageInputBar() 42 | } 43 | } 44 | } 45 | 46 | final class ExampleViewController: UITableViewController { 47 | 48 | // MARK: - Properties 49 | 50 | override var inputAccessoryView: UIView? { 51 | return messageInputBar 52 | } 53 | 54 | override var canBecomeFirstResponder: Bool { 55 | return true 56 | } 57 | 58 | /// The object that manages attachments 59 | lazy var attachmentManager: AttachmentManager = { [unowned self] in 60 | let manager = AttachmentManager() 61 | manager.delegate = self 62 | return manager 63 | }() 64 | 65 | /// The object that manages autocomplete 66 | lazy var autocompleteManager: AutocompleteManager = { [unowned self] in 67 | let manager = AutocompleteManager(for: self.messageInputBar.inputTextView) 68 | manager.delegate = self 69 | manager.dataSource = self 70 | return manager 71 | }() 72 | 73 | let users = ["nathantannar4", "SD10"] 74 | 75 | let hastags = ["MessageKit", "MessageInputBar"] 76 | 77 | // MARK: - MessageInputBar 78 | 79 | private let messageInputBar: MessageInputBar 80 | 81 | // MARK: Init 82 | 83 | init(messageInputBarStyle: MessageInputBarStyle) { 84 | self.messageInputBar = messageInputBarStyle.generate() 85 | super.init(nibName: nil, bundle: nil) 86 | } 87 | 88 | required init?(coder aDecoder: NSCoder) { 89 | fatalError("init(coder:) has not been implemented") 90 | } 91 | 92 | // MARK: - View Life Cycle 93 | 94 | override func viewDidLoad() { 95 | super.viewDidLoad() 96 | view.backgroundColor = .white 97 | tableView.keyboardDismissMode = .interactive 98 | messageInputBar.delegate = self 99 | messageInputBar.plugins = [attachmentManager, autocompleteManager] 100 | 101 | autocompleteManager.register(prefix: "@", with: [.font: UIFont.preferredFont(forTextStyle: .body),.foregroundColor: UIColor(red: 0, green: 122/255, blue: 1, alpha: 1),.backgroundColor: UIColor(red: 0, green: 122/255, blue: 1, alpha: 0.1)]) 102 | autocompleteManager.register(prefix: "#") 103 | 104 | // Want to return custom cells? Set the dataSource 105 | // attachmentManager.dataSource = self 106 | } 107 | 108 | } 109 | 110 | extension ExampleViewController: MessageInputBarDelegate { 111 | 112 | func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) { 113 | // Use to send the message 114 | messageInputBar.inputTextView.text = String() 115 | messageInputBar.invalidatePlugins() 116 | } 117 | 118 | func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) { 119 | // Use to send a typing indicator 120 | } 121 | 122 | func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) { 123 | // Use to change any other subview insets 124 | } 125 | 126 | } 127 | 128 | extension ExampleViewController: AttachmentManagerDelegate { 129 | 130 | 131 | // MARK: - AttachmentManagerDelegate 132 | 133 | func attachmentManager(_ manager: AttachmentManager, shouldBecomeVisible: Bool) { 134 | setAttachmentManager(active: shouldBecomeVisible) 135 | } 136 | 137 | func attachmentManager(_ manager: AttachmentManager, didReloadTo attachments: [AttachmentManager.Attachment]) { 138 | messageInputBar.sendButton.isEnabled = manager.attachments.count > 0 139 | } 140 | 141 | func attachmentManager(_ manager: AttachmentManager, didInsert attachment: AttachmentManager.Attachment, at index: Int) { 142 | messageInputBar.sendButton.isEnabled = manager.attachments.count > 0 143 | } 144 | 145 | func attachmentManager(_ manager: AttachmentManager, didRemove attachment: AttachmentManager.Attachment, at index: Int) { 146 | messageInputBar.sendButton.isEnabled = manager.attachments.count > 0 147 | } 148 | 149 | func attachmentManager(_ manager: AttachmentManager, didSelectAddAttachmentAt index: Int) { 150 | let imagePicker = UIImagePickerController() 151 | imagePicker.delegate = self 152 | imagePicker.sourceType = .photoLibrary 153 | present(imagePicker, animated: true, completion: nil) 154 | } 155 | 156 | // MARK: - AttachmentManagerDelegate Helper 157 | 158 | func setAttachmentManager(active: Bool) { 159 | 160 | let topStackView = messageInputBar.topStackView 161 | if active && !topStackView.arrangedSubviews.contains(attachmentManager.attachmentView) { 162 | topStackView.insertArrangedSubview(attachmentManager.attachmentView, at: topStackView.arrangedSubviews.count) 163 | topStackView.layoutIfNeeded() 164 | } else if !active && topStackView.arrangedSubviews.contains(attachmentManager.attachmentView) { 165 | topStackView.removeArrangedSubview(attachmentManager.attachmentView) 166 | topStackView.layoutIfNeeded() 167 | } 168 | } 169 | } 170 | 171 | extension ExampleViewController: AutocompleteManagerDelegate, AutocompleteManagerDataSource { 172 | 173 | // MARK: - AutocompleteManagerDataSource 174 | 175 | func autocompleteManager(_ manager: AutocompleteManager, autocompleteSourceFor prefix: String) -> [AutocompleteCompletion] { 176 | if prefix == "@" { 177 | return users.map { AutocompleteCompletion(text: $0) } 178 | } else if prefix == "#" { 179 | return hastags.map { AutocompleteCompletion(text: $0) } 180 | } 181 | return [] 182 | } 183 | 184 | func autocompleteManager(_ manager: AutocompleteManager, tableView: UITableView, cellForRowAt indexPath: IndexPath, for session: AutocompleteSession) -> UITableViewCell { 185 | 186 | guard let cell = tableView.dequeueReusableCell(withIdentifier: AutocompleteCell.reuseIdentifier, for: indexPath) as? AutocompleteCell else { 187 | fatalError("Oops, some unknown error occurred") 188 | } 189 | cell.textLabel?.attributedText = manager.attributedText(matching: session, fontSize: 15) 190 | return cell 191 | } 192 | 193 | // MARK: - AutocompleteManagerDelegate 194 | 195 | func autocompleteManager(_ manager: AutocompleteManager, shouldBecomeVisible: Bool) { 196 | setAutocompleteManager(active: shouldBecomeVisible) 197 | } 198 | 199 | // Optional 200 | func autocompleteManager(_ manager: AutocompleteManager, shouldRegister prefix: String, at range: NSRange) -> Bool { 201 | return true 202 | } 203 | 204 | // Optional 205 | func autocompleteManager(_ manager: AutocompleteManager, shouldUnregister prefix: String) -> Bool { 206 | return true 207 | } 208 | 209 | // Optional 210 | func autocompleteManager(_ manager: AutocompleteManager, shouldComplete prefix: String, with text: String) -> Bool { 211 | return true 212 | } 213 | 214 | // MARK: - AutocompleteManagerDelegate Helper 215 | 216 | func setAutocompleteManager(active: Bool) { 217 | 218 | let topStackView = messageInputBar.topStackView 219 | if active && !topStackView.arrangedSubviews.contains(autocompleteManager.tableView) { 220 | topStackView.insertArrangedSubview(autocompleteManager.tableView, at: topStackView.arrangedSubviews.count) 221 | topStackView.layoutIfNeeded() 222 | } else if !active && topStackView.arrangedSubviews.contains(autocompleteManager.tableView) { 223 | topStackView.removeArrangedSubview(autocompleteManager.tableView) 224 | topStackView.layoutIfNeeded() 225 | } 226 | } 227 | 228 | } 229 | 230 | extension ExampleViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 231 | 232 | private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 233 | 234 | dismiss(animated: true, completion: { 235 | if let pickedImage = info[UIImagePickerController.InfoKey.originalImage.rawValue] as? UIImage { 236 | let handled = self.attachmentManager.handleInput(of: pickedImage) 237 | if !handled { 238 | // throw error 239 | } 240 | } 241 | }) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Plugins/AttachmentManager/AttachmentManager.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | open class AttachmentManager: NSObject, InputPlugin { 28 | 29 | public enum Attachment { 30 | case image(UIImage) 31 | case url(URL) 32 | case data(Data) 33 | } 34 | 35 | // MARK: - Properties [Public] 36 | 37 | /// A protocol that can recieve notifications from the `AttachmentManager` 38 | open weak var delegate: AttachmentManagerDelegate? 39 | 40 | /// A protocol to passes data to the `AttachmentManager` 41 | open weak var dataSource: AttachmentManagerDataSource? 42 | 43 | open lazy var attachmentView: AttachmentCollectionView = { [weak self] in 44 | let attachmentView = AttachmentCollectionView() 45 | attachmentView.dataSource = self 46 | attachmentView.delegate = self 47 | return attachmentView 48 | }() 49 | 50 | /// The attachments that the managers holds 51 | private(set) public var attachments = [Attachment]() { didSet { reloadData() } } 52 | 53 | /// A flag you can use to determine if you want the manager to be always visible 54 | open var isPersistent = false { didSet { attachmentView.reloadData() } } 55 | 56 | /// A flag to determine if the AddAttachmentCell is visible 57 | open var showAddAttachmentCell = true { didSet { attachmentView.reloadData() } } 58 | 59 | // MARK: - Initialization 60 | 61 | public override init() { 62 | super.init() 63 | } 64 | 65 | // MARK: - InputManager 66 | 67 | open func reloadData() { 68 | attachmentView.reloadData() 69 | delegate?.attachmentManager(self, didReloadTo: attachments) 70 | delegate?.attachmentManager(self, shouldBecomeVisible: attachments.count > 0 || isPersistent) 71 | } 72 | 73 | /// Invalidates the `AttachmentManagers` session by removing all attachments 74 | open func invalidate() { 75 | attachments = [] 76 | } 77 | 78 | /// Appends the object to the attachments 79 | /// 80 | /// - Parameter object: The object to append 81 | open func handleInput(of object: AnyObject) -> Bool { 82 | let attachment: Attachment 83 | if let image = object as? UIImage { 84 | attachment = .image(image) 85 | } else if let url = object as? URL { 86 | attachment = .url(url) 87 | } else if let data = object as? Data { 88 | attachment = .data(data) 89 | } else { 90 | return false 91 | } 92 | insertAttachment(attachment, at: attachments.count) 93 | return true 94 | } 95 | 96 | // MARK: - API [Public] 97 | 98 | /// Performs an animated insertion of an attachment at an index 99 | /// 100 | /// - Parameter index: The index to insert the attachment at 101 | open func insertAttachment(_ attachment: Attachment, at index: Int) { 102 | 103 | attachmentView.performBatchUpdates({ 104 | self.attachments.insert(attachment, at: index) 105 | self.attachmentView.insertItems(at: [IndexPath(row: index, section: 0)]) 106 | }, completion: { success in 107 | self.attachmentView.reloadData() 108 | self.delegate?.attachmentManager(self, didInsert: attachment, at: index) 109 | self.delegate?.attachmentManager(self, shouldBecomeVisible: self.attachments.count > 0 || self.isPersistent) 110 | }) 111 | } 112 | 113 | /// Performs an animated removal of an attachment at an index 114 | /// 115 | /// - Parameter index: The index to remove the attachment at 116 | open func removeAttachment(at index: Int) { 117 | 118 | let attachment = attachments[index] 119 | attachmentView.performBatchUpdates({ 120 | self.attachments.remove(at: index) 121 | self.attachmentView.deleteItems(at: [IndexPath(row: index, section: 0)]) 122 | }, completion: { success in 123 | self.attachmentView.reloadData() 124 | self.delegate?.attachmentManager(self, didRemove: attachment, at: index) 125 | self.delegate?.attachmentManager(self, shouldBecomeVisible: self.attachments.count > 0 || self.isPersistent) 126 | }) 127 | } 128 | 129 | } 130 | 131 | extension AttachmentManager: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 132 | 133 | // MARK: - UICollectionViewDelegate 134 | 135 | final public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 136 | if indexPath.row == attachments.count { 137 | delegate?.attachmentManager(self, didSelectAddAttachmentAt: indexPath.row) 138 | delegate?.attachmentManager(self, shouldBecomeVisible: attachments.count > 0 || isPersistent) 139 | } 140 | } 141 | 142 | // MARK: - UICollectionViewDataSource 143 | 144 | final public func numberOfItems(inSection section: Int) -> Int { 145 | return 1 146 | } 147 | 148 | final public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 149 | return attachments.count + (showAddAttachmentCell ? 1 : 0) 150 | } 151 | 152 | final public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 153 | 154 | if indexPath.row == attachments.count && showAddAttachmentCell { 155 | return addAttachmentCell(in: collectionView, at: indexPath) 156 | } 157 | 158 | let attachment = attachments[indexPath.row] 159 | 160 | if let cell = dataSource?.attachmentManager(self, cellFor: attachment, at: indexPath.row) { 161 | return cell 162 | } else { 163 | 164 | // Only images are supported by default 165 | switch attachment { 166 | case .image(let image): 167 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageAttachmentCell.reuseIdentifier, for: indexPath) as? ImageAttachmentCell else { 168 | fatalError() 169 | } 170 | cell.attachment = attachment 171 | cell.indexPath = indexPath 172 | cell.manager = self 173 | cell.imageView.image = image 174 | return cell 175 | default: 176 | return collectionView.dequeueReusableCell(withReuseIdentifier: AttachmentCell.reuseIdentifier, for: indexPath) as! AttachmentCell 177 | } 178 | 179 | } 180 | } 181 | 182 | // MARK: - UICollectionViewDelegateFlowLayout 183 | 184 | final public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 185 | 186 | var height = attachmentView.intrinsicContentHeight 187 | if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { 188 | height -= (layout.sectionInset.bottom + layout.sectionInset.top + collectionView.contentInset.top + collectionView.contentInset.bottom) 189 | } 190 | return CGSize(width: height, height: height) 191 | } 192 | 193 | private func addAttachmentCell(in collectionView: UICollectionView, at indexPath: IndexPath) -> AttachmentCell { 194 | 195 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AttachmentCell.reuseIdentifier, for: indexPath) as? AttachmentCell else { 196 | fatalError() 197 | } 198 | cell.deleteButton.isHidden = true 199 | // Draw a plus 200 | let frame = CGRect(origin: CGPoint(x: cell.bounds.origin.x, 201 | y: cell.bounds.origin.y), 202 | size: CGSize(width: cell.bounds.width - cell.padding.left - cell.padding.right, 203 | height: cell.bounds.height - cell.padding.top - cell.padding.bottom)) 204 | let strokeWidth: CGFloat = 3 205 | let length: CGFloat = frame.width / 2 206 | let vLayer = CAShapeLayer() 207 | vLayer.path = UIBezierPath(roundedRect: CGRect(x: frame.midX - (strokeWidth / 2), 208 | y: frame.midY - (length / 2), 209 | width: strokeWidth, 210 | height: length), cornerRadius: 5).cgPath 211 | vLayer.fillColor = UIColor.lightGray.cgColor 212 | let hLayer = CAShapeLayer() 213 | hLayer.path = UIBezierPath(roundedRect: CGRect(x: frame.midX - (length / 2), 214 | y: frame.midY - (strokeWidth / 2), 215 | width: length, 216 | height: strokeWidth), cornerRadius: 5).cgPath 217 | hLayer.fillColor = UIColor.lightGray.cgColor 218 | cell.containerView.layer.addSublayer(vLayer) 219 | cell.containerView.layer.addSublayer(hLayer) 220 | return cell 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /Sources/Controls/InputBarButtonItem.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import UIKit 26 | 27 | /** 28 | A subclass of UIButton that conforms to InputItem 29 | 30 | ## Important Notes ## 31 | 1. Intended to be used in an `InputStackView` 32 | */ 33 | open class InputBarButtonItem: UIButton, InputItem { 34 | 35 | /// The spacing properties of the InputBarButtonItem 36 | /// 37 | /// - fixed: The spacing is fixed 38 | /// - flexible: The spacing is flexible 39 | /// - none: There is no spacing 40 | public enum Spacing { 41 | case fixed(CGFloat) 42 | case flexible 43 | case none 44 | } 45 | 46 | public typealias InputBarButtonItemAction = ((InputBarButtonItem) -> Void) 47 | 48 | // MARK: - Properties 49 | 50 | /// A weak reference to the MessageInputBar that the InputBarButtonItem used in 51 | open weak var messageInputBar: MessageInputBar? 52 | 53 | /// The spacing property of the InputBarButtonItem that determines the contentHuggingPriority and any 54 | /// additional space to the intrinsicContentSize 55 | open var spacing: Spacing = .none { 56 | didSet { 57 | switch spacing { 58 | case .flexible: 59 | setContentHuggingPriority(UILayoutPriority(rawValue: 1), for: .horizontal) 60 | case .fixed: 61 | setContentHuggingPriority(UILayoutPriority(rawValue: 1000), for: .horizontal) 62 | case .none: 63 | setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) 64 | } 65 | } 66 | } 67 | 68 | /// When not nil this size overrides the intrinsicContentSize 69 | private var size: CGSize? = CGSize(width: 20, height: 20) { 70 | didSet { 71 | invalidateIntrinsicContentSize() 72 | } 73 | } 74 | 75 | open override var intrinsicContentSize: CGSize { 76 | var contentSize = size ?? super.intrinsicContentSize 77 | switch spacing { 78 | case .fixed(let width): 79 | contentSize.width += width 80 | case .flexible, .none: 81 | break 82 | } 83 | return contentSize 84 | } 85 | 86 | /// A reference to the stack view position that the InputBarButtonItem is held in 87 | open var parentStackViewPosition: InputStackView.Position? 88 | 89 | /// The title for the UIControlState.normal 90 | open var title: String? { 91 | get { 92 | return title(for: .normal) 93 | } 94 | set { 95 | setTitle(newValue, for: .normal) 96 | } 97 | } 98 | 99 | /// The image for the UIControlState.normal 100 | open var image: UIImage? { 101 | get { 102 | return image(for: .normal) 103 | } 104 | set { 105 | setImage(newValue, for: .normal) 106 | } 107 | } 108 | 109 | /// Calls the onSelectedAction or onDeselectedAction when set 110 | open override var isHighlighted: Bool { 111 | get { 112 | return super.isHighlighted 113 | } 114 | set { 115 | guard newValue != isHighlighted else { return } 116 | super.isHighlighted = newValue 117 | if newValue { 118 | onSelectedAction?(self) 119 | } else { 120 | onDeselectedAction?(self) 121 | } 122 | 123 | } 124 | } 125 | 126 | /// Calls the onEnabledAction or onDisabledAction when set 127 | open override var isEnabled: Bool { 128 | didSet { 129 | if isEnabled { 130 | onEnabledAction?(self) 131 | } else { 132 | onDisabledAction?(self) 133 | } 134 | } 135 | } 136 | 137 | // MARK: - Reactive Hooks 138 | 139 | private var onTouchUpInsideAction: InputBarButtonItemAction? 140 | private var onKeyboardEditingBeginsAction: InputBarButtonItemAction? 141 | private var onKeyboardEditingEndsAction: InputBarButtonItemAction? 142 | private var onTextViewDidChangeAction: ((InputBarButtonItem, InputTextView) -> Void)? 143 | private var onSelectedAction: InputBarButtonItemAction? 144 | private var onDeselectedAction: InputBarButtonItemAction? 145 | private var onEnabledAction: InputBarButtonItemAction? 146 | private var onDisabledAction: InputBarButtonItemAction? 147 | 148 | // MARK: - Initialization 149 | 150 | public convenience init() { 151 | self.init(frame: .zero) 152 | } 153 | 154 | public override init(frame: CGRect) { 155 | super.init(frame: frame) 156 | setup() 157 | } 158 | 159 | required public init?(coder aDecoder: NSCoder) { 160 | super.init(coder: aDecoder) 161 | setup() 162 | } 163 | 164 | // MARK: - Setup 165 | 166 | /// Sets up the default properties 167 | open func setup() { 168 | contentVerticalAlignment = .center 169 | contentHorizontalAlignment = .center 170 | imageView?.contentMode = .scaleAspectFit 171 | setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) 172 | setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .vertical) 173 | setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 1), for: .normal) 174 | setTitleColor(UIColor(red: 0, green: 122/255, blue: 1, alpha: 0.3), for: .highlighted) 175 | setTitleColor(.lightGray, for: .disabled) 176 | adjustsImageWhenHighlighted = false 177 | addTarget(self, action: #selector(InputBarButtonItem.touchUpInsideAction), for: .touchUpInside) 178 | } 179 | 180 | // MARK: - Size Adjustment 181 | 182 | /// Sets the size of the InputBarButtonItem which overrides the intrinsicContentSize. When set to nil 183 | /// the default intrinsicContentSize is used. The new size will be laid out in the UIStackView that 184 | /// the InputBarButtonItem is held in 185 | /// 186 | /// - Parameters: 187 | /// - newValue: The new size 188 | /// - animated: If the layout should be animated 189 | open func setSize(_ newValue: CGSize?, animated: Bool) { 190 | size = newValue 191 | if animated, let position = parentStackViewPosition { 192 | messageInputBar?.performLayout(animated) { [weak self] in 193 | self?.messageInputBar?.layoutStackViews([position]) 194 | } 195 | } 196 | } 197 | 198 | // MARK: - Hook Setup Methods 199 | 200 | /// Used to setup your own initial properties 201 | /// 202 | /// - Parameter item: A reference to Self 203 | /// - Returns: Self 204 | @discardableResult 205 | open func configure(_ item: InputBarButtonItemAction) -> Self { 206 | item(self) 207 | return self 208 | } 209 | 210 | /// Sets the onKeyboardEditingBeginsAction 211 | /// 212 | /// - Parameter action: The new onKeyboardEditingBeginsAction 213 | /// - Returns: Self 214 | @discardableResult 215 | open func onKeyboardEditingBegins(_ action: @escaping InputBarButtonItemAction) -> Self { 216 | onKeyboardEditingBeginsAction = action 217 | return self 218 | } 219 | 220 | /// Sets the onKeyboardEditingEndsAction 221 | /// 222 | /// - Parameter action: The new onKeyboardEditingEndsAction 223 | /// - Returns: Self 224 | @discardableResult 225 | open func onKeyboardEditingEnds(_ action: @escaping InputBarButtonItemAction) -> Self { 226 | onKeyboardEditingEndsAction = action 227 | return self 228 | } 229 | 230 | /// Sets the onTextViewDidChangeAction 231 | /// 232 | /// - Parameter action: The new onTextViewDidChangeAction 233 | /// - Returns: Self 234 | @discardableResult 235 | open func onTextViewDidChange(_ action: @escaping (_ item: InputBarButtonItem, _ textView: InputTextView) -> Void) -> Self { 236 | onTextViewDidChangeAction = action 237 | return self 238 | } 239 | 240 | /// Sets the onTouchUpInsideAction 241 | /// 242 | /// - Parameter action: The new onTouchUpInsideAction 243 | /// - Returns: Self 244 | @discardableResult 245 | open func onTouchUpInside(_ action: @escaping InputBarButtonItemAction) -> Self { 246 | onTouchUpInsideAction = action 247 | return self 248 | } 249 | 250 | /// Sets the onSelectedAction 251 | /// 252 | /// - Parameter action: The new onSelectedAction 253 | /// - Returns: Self 254 | @discardableResult 255 | open func onSelected(_ action: @escaping InputBarButtonItemAction) -> Self { 256 | onSelectedAction = action 257 | return self 258 | } 259 | 260 | /// Sets the onDeselectedAction 261 | /// 262 | /// - Parameter action: The new onDeselectedAction 263 | /// - Returns: Self 264 | @discardableResult 265 | open func onDeselected(_ action: @escaping InputBarButtonItemAction) -> Self { 266 | onDeselectedAction = action 267 | return self 268 | } 269 | 270 | /// Sets the onEnabledAction 271 | /// 272 | /// - Parameter action: The new onEnabledAction 273 | /// - Returns: Self 274 | @discardableResult 275 | open func onEnabled(_ action: @escaping InputBarButtonItemAction) -> Self { 276 | onEnabledAction = action 277 | return self 278 | } 279 | 280 | /// Sets the onDisabledAction 281 | /// 282 | /// - Parameter action: The new onDisabledAction 283 | /// - Returns: Self 284 | @discardableResult 285 | open func onDisabled(_ action: @escaping InputBarButtonItemAction) -> Self { 286 | onDisabledAction = action 287 | return self 288 | } 289 | 290 | // MARK: - InputItem Protocol 291 | 292 | /// Executes the onTextViewDidChangeAction with the given textView 293 | /// 294 | /// - Parameter textView: A reference to the InputTextView 295 | open func textViewDidChangeAction(with textView: InputTextView) { 296 | onTextViewDidChangeAction?(self, textView) 297 | } 298 | 299 | /// Executes the onKeyboardEditingEndsAction 300 | open func keyboardEditingEndsAction() { 301 | onKeyboardEditingEndsAction?(self) 302 | } 303 | 304 | /// Executes the onKeyboardEditingBeginsAction 305 | open func keyboardEditingBeginsAction() { 306 | onKeyboardEditingBeginsAction?(self) 307 | } 308 | 309 | /// Executes the onTouchUpInsideAction 310 | @objc 311 | open func touchUpInsideAction() { 312 | onTouchUpInsideAction?(self) 313 | } 314 | 315 | // MARK: - Static Spacers 316 | 317 | /// An InputBarButtonItem that's spacing property is set to be .flexible 318 | public static var flexibleSpace: InputBarButtonItem { 319 | let item = InputBarButtonItem() 320 | item.setSize(.zero, animated: false) 321 | item.spacing = .flexible 322 | return item 323 | } 324 | 325 | /// An InputBarButtonItem that's spacing property is set to be .fixed with the width arguement 326 | public static func fixedSpace(_ width: CGFloat) -> InputBarButtonItem { 327 | let item = InputBarButtonItem() 328 | item.setSize(.zero, animated: false) 329 | item.spacing = .fixed(width) 330 | return item 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /Example/Example/Lorem.swift: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 MessageKit 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 all 14 | 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 THE 22 | SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | public class Lorem { 28 | private static let wordList = [ 29 | "alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", 30 | "accusantium", "doloremque", "aperiam", "eaque","ipsa", "quae", "ab", 31 | "illo", "inventore", "veritatis", "et", "quasi", "architecto", 32 | "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", 33 | "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", 34 | "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", 35 | "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", 36 | "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", 37 | "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", 38 | "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", 39 | "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", 40 | "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", 41 | "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", 42 | "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", 43 | "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", 44 | "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", 45 | "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", 46 | "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", 47 | "dolores", "et", "quas", "molestias", "excepturi", "sint", 48 | "occaecati", "cupiditate", "non", "provident", "sed", "ut", 49 | "perspiciatis", "unde", "omnis", "iste", "natus", "error", 50 | "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", 51 | "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", 52 | "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", 53 | "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", 54 | "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", 55 | "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", 56 | "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", 57 | "est", "omnis", "dolor", "repellendus", "temporibus", "autem", 58 | "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", 59 | "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", 60 | "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", 61 | "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", 62 | "voluptates", "repudiandae", "sint", "et", "molestiae", "non", 63 | "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", 64 | "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", 65 | "maiores", "doloribus", "asperiores", "repellat" 66 | ] 67 | 68 | /** 69 | Return a random word. 70 | 71 | - returns: Returns a random word. 72 | */ 73 | public class func word() -> String { 74 | return wordList.random()! 75 | } 76 | 77 | /** 78 | Return an array of `count` words. 79 | 80 | - parameter count: The number of words to return. 81 | 82 | - returns: Returns an array of `count` words. 83 | */ 84 | public class func words(nbWords: Int = 3) -> [String] { 85 | return wordList.random(nbWords) 86 | } 87 | 88 | /** 89 | Return a string of `count` words. 90 | 91 | - parameter count: The number of words the string should contain. 92 | 93 | - returns: Returns a string of `count` words. 94 | */ 95 | public class func words(nbWords : Int = 3) -> String { 96 | return words(nbWords: nbWords).joined(separator: " ") 97 | } 98 | 99 | /** 100 | Generate a sentence of `nbWords` words. 101 | - parameter nbWords: The number of words the sentence should contain. 102 | - parameter variable: If `true`, the number of words will vary between 103 | +/- 40% of `nbWords`. 104 | - returns: 105 | */ 106 | public class func sentence(nbWords : Int = 6, variable : Bool = true) -> String { 107 | if nbWords <= 0 { 108 | return "" 109 | } 110 | 111 | let result : String = self.words(nbWords: variable ? nbWords.randomize(variation: 40) : nbWords) 112 | 113 | return result.firstCapitalized + "." 114 | } 115 | 116 | /** 117 | Generate an array of sentences. 118 | - parameter nbSentences: The number of sentences to generate. 119 | 120 | - returns: Returns an array of random sentences. 121 | */ 122 | public class func sentences(nbSentences: Int = 3) -> [String] { 123 | return (0.. String { 132 | return sentences(nbSentences: nbSentences).joined(separator: "") 133 | } 134 | 135 | /** 136 | Generate a paragraph with `nbSentences` random sentences. 137 | - parameter nbSentences: The number of sentences the paragraph should 138 | contain. 139 | - parameter variable: If `true`, the number of sentences will vary 140 | between +/- 40% of `nbSentences`. 141 | - returns: Returns a paragraph with `nbSentences` random sentences. 142 | */ 143 | public class func paragraph(nbSentences : Int = 3, variable : Bool = true) -> String { 144 | if nbSentences <= 0 { 145 | return "" 146 | } 147 | 148 | return sentences(nbSentences: variable ? nbSentences.randomize(variation: 40) : nbSentences).joined(separator: " ") 149 | } 150 | 151 | /** 152 | Generate an array of random paragraphs. 153 | - parameter nbParagraphs: The number of paragraphs to generate. 154 | - returns: Returns an array of `nbParagraphs` paragraphs. 155 | */ 156 | public class func paragraphs(nbParagraphs : Int = 3) -> [String] { 157 | return (0.. String { 166 | return paragraphs(nbParagraphs: nbParagraphs).joined(separator: "\n\n") 167 | } 168 | 169 | /** 170 | Generate a string of at most `maxNbChars` characters. 171 | - parameter maxNbChars: The maximum number of characters the string 172 | should contain. 173 | - returns: Returns a string of at most `maxNbChars` characters. 174 | */ 175 | public class func text(maxNbChars : Int = 200) -> String { 176 | var result : [String] = [] 177 | 178 | if maxNbChars < 5 { 179 | return "" 180 | } else if maxNbChars < 25 { 181 | while result.count == 0 { 182 | var size = 0 183 | 184 | while size < maxNbChars { 185 | let w = (size != 0 ? " " : "") + word() 186 | result.append(w) 187 | size += w.count 188 | } 189 | 190 | _ = result.popLast() 191 | } 192 | } else if maxNbChars < 100 { 193 | while result.count == 0 { 194 | var size = 0 195 | 196 | while size < maxNbChars { 197 | let s = (size != 0 ? " " : "") + sentence() 198 | result.append(s) 199 | size += s.count 200 | } 201 | 202 | _ = result.popLast() 203 | } 204 | } else { 205 | while result.count == 0 { 206 | var size = 0 207 | 208 | while size < maxNbChars { 209 | let p = (size != 0 ? "\n" : "") + paragraph() 210 | result.append(p) 211 | size += p.count 212 | } 213 | 214 | _ = result.popLast() 215 | } 216 | } 217 | 218 | return result.joined(separator: "") 219 | } 220 | } 221 | 222 | extension String { 223 | var firstCapitalized: String { 224 | var string = self 225 | string.replaceSubrange(string.startIndex...string.startIndex, with: String(string[string.startIndex]).capitalized) 226 | return string 227 | } 228 | } 229 | 230 | public extension Array { 231 | /** 232 | Shuffle the array in-place using the Fisher-Yates algorithm. 233 | */ 234 | public mutating func shuffle() { 235 | for i in 0..<(count - 1) { 236 | let j = Int(arc4random_uniform(UInt32(count - i))) + i 237 | if j != i { 238 | self.swapAt(i, j) 239 | } 240 | } 241 | } 242 | 243 | /** 244 | Return a shuffled version of the array using the Fisher-Yates 245 | algorithm. 246 | 247 | - returns: Returns a shuffled version of the array. 248 | */ 249 | public func shuffled() -> [Element] { 250 | var list = self 251 | list.shuffle() 252 | 253 | return list 254 | } 255 | 256 | /** 257 | Return a random element from the array. 258 | - returns: Returns a random element from the array or `nil` if the 259 | array is empty. 260 | */ 261 | public func random() -> Element? { 262 | return (count > 0) ? self.shuffled()[0] : nil 263 | } 264 | 265 | /** 266 | Return a random subset of `cnt` elements from the array. 267 | - returns: Returns a random subset of `cnt` elements from the array. 268 | */ 269 | public func random(_ count : Int = 1) -> [Element] { 270 | let result = shuffled() 271 | 272 | return (count > result.count) ? result : Array(result[0.. Int { 287 | precondition(min <= max, "attempt to call random() with min > max") 288 | 289 | let diff = UInt(bitPattern: max &- min) 290 | let result = UInt.random(min: 0, max: diff) 291 | 292 | return min + Int(bitPattern: result) 293 | } 294 | 295 | public func randomize(variation : Int) -> Int { 296 | let multiplier = Double(Int.random(min: 100 - variation, max: 100 + variation)) / 100 297 | let randomized = Double(self) * multiplier 298 | 299 | return Int(randomized) + 1 300 | } 301 | } 302 | 303 | private extension UInt { 304 | static func random(min : UInt, max : UInt) -> UInt { 305 | precondition(min <= max, "attempt to call random() with min > max") 306 | 307 | if min == UInt.min && max == UInt.max { 308 | var result : UInt = 0 309 | arc4random_buf(&result, MemoryLayout.size(ofValue: result)) 310 | 311 | return result 312 | } else { 313 | let range = max - min + 1 314 | let limit = UInt.max - UInt.max % range 315 | var result : UInt = 0 316 | 317 | repeat { 318 | arc4random_buf(&result, MemoryLayout.size(ofValue: result)) 319 | } while result >= limit 320 | 321 | result = result % range 322 | 323 | return min + result 324 | } 325 | } 326 | } 327 | 328 | --------------------------------------------------------------------------------