├── .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 | [](https://github.com/Carthage/Carthage)
6 |
7 |
9 |
10 |
11 |
13 |
14 |
15 |
17 |
18 |
19 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------