├── Cartfile ├── Cartfile.resolved ├── Gemfile ├── Example ├── Sources │ ├── Models │ │ ├── User.swift │ │ └── Message.swift │ ├── AppDelegate.swift │ ├── Views │ │ ├── MessageCell.swift │ │ └── MessageInputBar.swift │ └── ViewControllers │ │ └── MessageListViewController.swift ├── RxKeyboardExample.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── RxKeyboardExample.xcworkspace │ └── contents.xcworkspacedata ├── Podfile ├── README.md ├── Resources │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── Supporting Files │ └── Info.plist └── Podfile.lock ├── Package.resolved ├── .gitignore ├── Package.swift ├── RxKeyboard.podspec ├── LICENSE ├── scripts └── carthage.sh ├── RxKeyboard.json ├── .travis.yml ├── Gemfile.lock ├── README.md └── Sources └── RxKeyboard └── RxKeyboard.swift /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" ~> 6.0.0 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" "6.0.0" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.10' 4 | gem 'swiftproj' 5 | -------------------------------------------------------------------------------- /Example/Sources/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // RxKeyboard 4 | // 5 | // Created by Suyeol Jeon on 18/01/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | enum User { 10 | case other 11 | case me 12 | } 13 | -------------------------------------------------------------------------------- /Example/Sources/Models/Message.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message.swift 3 | // RxKeyboard 4 | // 5 | // Created by Suyeol Jeon on 18/01/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | struct Message { 10 | var user: User 11 | var text: String 12 | } 13 | -------------------------------------------------------------------------------- /Example/RxKeyboardExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RxKeyboardExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | 3 | target 'RxKeyboardExample' do 4 | use_frameworks! 5 | 6 | pod 'RxKeyboard', :path => '../' 7 | pod 'Then' 8 | pod 'UITextView+Placeholder' 9 | pod 'ReusableKit' 10 | pod 'SnapKit' 11 | pod 'ManualLayout' 12 | pod 'SwiftyColor' 13 | pod 'SwiftyImage' 14 | pod 'CGFloatLiteral' 15 | end 16 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "RxSwift", 6 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c8742ed97fc2f0c015a5ea5eddefb064cd7532d2", 10 | "version": "6.0.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | sourcekitten-output.json 4 | docs/ 5 | /*.xcodeproj 6 | **/xcuserdata 7 | **/xcshareddata 8 | *.xcconfig 9 | *.framework.zip 10 | 11 | # CocoaPods 12 | 13 | Pods/ 14 | Examples/**/Podfile.lock 15 | 16 | # Carthage 17 | 18 | Carthage/ 19 | 20 | # Various 21 | 22 | .DS_Store 23 | 24 | # Swift Package Manager 25 | 26 | .build/ 27 | Packages/ 28 | .swiftpm 29 | 30 | # AppCode 31 | 32 | .idea/ -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RxKeyboard", 7 | products: [ 8 | .library(name: "RxKeyboard", targets: ["RxKeyboard"]), 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.0.0")), 12 | ], 13 | targets: [ 14 | .target(name: "RxKeyboard", dependencies: ["RxSwift", "RxCocoa"]), 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /Example/README.md: -------------------------------------------------------------------------------- 1 | # RxKeyboardExample 2 | 3 | This is an example messenger application using RxKeyboard. 4 | 5 | ![rxkeyboard-example](https://cloud.githubusercontent.com/assets/931655/22062707/625eea7a-ddbe-11e6-9984-529abae1bd1a.gif) 6 | 7 | ## Building Project 8 | 9 | 1. Install CocoaPods libraries. 10 | 11 | ```console 12 | $ pod install 13 | ``` 14 | 15 | 2. Open **`RxKeyboardExample.xcworkspace`** file. 16 | 3. Press + R to build and run the project. 17 | -------------------------------------------------------------------------------- /RxKeyboard.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'RxKeyboard' 3 | s.version = '2.0.0' 4 | s.summary = 'Reactive Keyboard in iOS' 5 | s.homepage = 'https://github.com/RxSwiftCommunity/RxKeyboard' 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = { 'Suyeol Jeon' => 'devxoul@gmail.com' } 8 | s.source = { :git => 'https://github.com/RxSwiftCommunity/RxKeyboard.git', 9 | :tag => s.version.to_s } 10 | s.source_files = 'Sources/**/*.swift' 11 | s.frameworks = 'UIKit' 12 | s.requires_arc = true 13 | s.swift_version = "5.1" 14 | 15 | s.dependency 'RxSwift', '~> 6.0' 16 | s.dependency 'RxCocoa', '~> 6.0' 17 | 18 | s.ios.deployment_target = '9.0' 19 | end 20 | -------------------------------------------------------------------------------- /Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Suyeol Jeon (xoul.kr) 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 | -------------------------------------------------------------------------------- /scripts/carthage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # carthage.sh 4 | # Usage example: ./carthage.sh build --platform iOS 5 | 6 | set -euo pipefail 7 | 8 | xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX) 9 | trap 'rm -f "$xcconfig"' INT TERM HUP EXIT 10 | 11 | # For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise 12 | # the build will fail on lipo due to duplicate architectures. 13 | 14 | CURRENT_XCODE_VERSION=$(xcodebuild -version | grep "Build version" | cut -d' ' -f3) 15 | echo "EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$CURRENT_XCODE_VERSION = arm64 arm64e armv7 armv7s armv6 armv8" >> $xcconfig 16 | 17 | echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >> $xcconfig 18 | echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig 19 | 20 | export XCODE_XCCONFIG_FILE="$xcconfig" 21 | carthage build "$@" -------------------------------------------------------------------------------- /RxKeyboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.7.0": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.7.0/RxKeyboard.framework.zip", 3 | "0.7.1": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.7.1/RxKeyboard.framework.zip", 4 | "0.8.0": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.8.0/RxKeyboard.framework.zip", 5 | "0.8.1": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.8.1/RxKeyboard.framework.zip", 6 | "0.8.2": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.8.2/RxKeyboard.framework.zip", 7 | "0.8.3": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.8.3/RxKeyboard.framework.zip", 8 | "0.9.0": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.9.0/RxKeyboard.framework.zip", 9 | "0.9.1": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.9.1/RxKeyboard.framework.zip", 10 | "0.9.2": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/0.9.2/RxKeyboard.framework.zip", 11 | "1.0.0": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/1.0.0/RxKeyboard.framework.zip", 12 | "2.0.0": "https://github.com/RxSwiftCommunity/RxKeyboard/releases/download/2.0.0/RxKeyboard.framework.zip", 13 | } 14 | -------------------------------------------------------------------------------- /Example/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Example/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RxKeyboardExample 4 | // 5 | // Created by Suyeol Jeon on 09/10/2016. 6 | // Copyright © 2016 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import CGFloatLiteral 12 | import ManualLayout 13 | import SnapKit 14 | import SwiftyColor 15 | import Then 16 | import UITextView_Placeholder 17 | 18 | @UIApplicationMain 19 | final class AppDelegate: UIResponder, UIApplicationDelegate { 20 | 21 | var window: UIWindow? 22 | 23 | #if swift(>=4.2) 24 | typealias ApplicationLaunchOptionsKey = UIApplication.LaunchOptionsKey 25 | #else 26 | typealias ApplicationLaunchOptionsKey = UIApplicationLaunchOptionsKey 27 | #endif 28 | 29 | func application( 30 | _ application: UIApplication, 31 | didFinishLaunchingWithOptions launchOptions: [ApplicationLaunchOptionsKey: Any]? 32 | ) -> Bool { 33 | let window = UIWindow(frame: UIScreen.main.bounds) 34 | window.backgroundColor = .white 35 | window.makeKeyAndVisible() 36 | 37 | let messageListViewController = MessageListViewController() 38 | let navigationController = UINavigationController(rootViewController: messageListViewController) 39 | window.rootViewController = navigationController 40 | 41 | self.window = window 42 | return true 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Example/Resources/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 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CGFloatLiteral (0.5.0) 3 | - ManualLayout (1.3.0) 4 | - ReusableKit (3.0.0): 5 | - ReusableKit/Core (= 3.0.0) 6 | - ReusableKit/Core (3.0.0) 7 | - RxCocoa (6.0.0): 8 | - RxRelay (= 6.0.0) 9 | - RxSwift (= 6.0.0) 10 | - RxKeyboard (2.0.0): 11 | - RxCocoa (~> 6.0) 12 | - RxSwift (~> 6.0) 13 | - RxRelay (6.0.0): 14 | - RxSwift (= 6.0.0) 15 | - RxSwift (6.0.0) 16 | - SnapKit (5.0.1) 17 | - SwiftyColor (1.2.1) 18 | - SwiftyImage (1.6.0) 19 | - Then (2.7.0) 20 | - "UITextView+Placeholder (1.4.0)" 21 | 22 | DEPENDENCIES: 23 | - CGFloatLiteral 24 | - ManualLayout 25 | - ReusableKit 26 | - RxKeyboard (from `../`) 27 | - SnapKit 28 | - SwiftyColor 29 | - SwiftyImage 30 | - Then 31 | - "UITextView+Placeholder" 32 | 33 | SPEC REPOS: 34 | trunk: 35 | - CGFloatLiteral 36 | - ManualLayout 37 | - ReusableKit 38 | - RxCocoa 39 | - RxRelay 40 | - RxSwift 41 | - SnapKit 42 | - SwiftyColor 43 | - SwiftyImage 44 | - Then 45 | - "UITextView+Placeholder" 46 | 47 | EXTERNAL SOURCES: 48 | RxKeyboard: 49 | :path: "../" 50 | 51 | SPEC CHECKSUMS: 52 | CGFloatLiteral: 0328648f666e3cb2263d5ee3972df9d320786d25 53 | ManualLayout: 68ac8cfa6b5f656f7a9fadec3730208b95986880 54 | ReusableKit: e5f853ad4652e411f96b6119b2488afa12929be6 55 | RxCocoa: 3f79328fafa3645b34600f37c31e64c73ae3a80e 56 | RxKeyboard: aefd4787ca8be28a4470cb871141fb50e105f900 57 | RxRelay: 8d593be109c06ea850df027351beba614b012ffb 58 | RxSwift: c14e798c59b9f6e9a2df8fd235602e85cc044295 59 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb 60 | SwiftyColor: a2f468071c1b96be1ba41a0be7cf70d645cd7dcc 61 | SwiftyImage: 65c71a147f417fc0fe137d17b135aaa666279a09 62 | Then: acfe0be7e98221c6204e12f8161459606d5d822d 63 | "UITextView+Placeholder": d7b0c400921f66523f3a85d74f774512e14f6502 64 | 65 | PODFILE CHECKSUM: 217b0b1c7b2faddc54272a5bde63843c54f48190 66 | 67 | COCOAPODS: 1.10.0 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode12.3 2 | language: objective-c 3 | sudo: required 4 | env: 5 | global: 6 | - PROJECT="RxKeyboard.xcodeproj" 7 | - SCHEME="RxKeyboard-Package" 8 | - IOS_SDK="iphonesimulator" 9 | - MACOS_SDK="macosx11.0" 10 | - TVOS_SDK="appletvsimulator9.0" 11 | - WATCHOS_SDK="watchsimulator3.0" 12 | - FRAMEWORK="RxKeyboard" 13 | matrix: 14 | - SDK="$IOS_SDK" TEST=0 SWIFT_VERSION=5.1 DESTINATION="platform=iOS Simulator,name=iPhone 8" 15 | 16 | install: 17 | - swift --version 18 | - bundle install 19 | 20 | before_script: 21 | - set -o pipefail 22 | - swift package generate-xcodeproj 23 | 24 | script: 25 | - if [ $TEST == 1 ]; then 26 | xcodebuild clean SWIFT_VERSION=${SWIFT_VERSION} build test 27 | -project "$PROJECT" 28 | -scheme "$SCHEME" 29 | -sdk "$SDK" 30 | -destination "$DESTINATION" 31 | -configuration Debug 32 | -enableCodeCoverage YES 33 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 34 | else 35 | xcodebuild clean SWIFT_VERSION=${SWIFT_VERSION} build 36 | -project "$PROJECT" 37 | -scheme "$SCHEME" 38 | -sdk "$SDK" 39 | -destination "$DESTINATION" 40 | -configuration Debug 41 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 42 | fi 43 | - bundle exec pod repo update 44 | - bundle exec pod lib lint --swift-version=${SWIFT_VERSION} --verbose --allow-warnings --fail-fast 45 | 46 | before_deploy: 47 | - bundle exec swiftproj generate-xcconfig --podspec RxKeyboard.podspec 48 | - bundle exec swiftproj generate-xcodeproj --xcconfig-overrides Config.xcconfig 49 | - bundle exec swiftproj configure-scheme --project RxKeyboard.xcodeproj --scheme RxKeyboard-Package --buildable-targets RxKeyboard 50 | - bundle exec swiftproj remove-framework --project RxKeyboard.xcodeproj --target RxKeyboard --framework RxCocoa.framework 51 | - bundle exec swiftproj remove-framework --project RxKeyboard.xcodeproj --target RxKeyboard --framework RxCocoaRuntime.framework 52 | - ./carthage.sh bootstrap 53 | - carthage build --no-skip-current --verbose | xcpretty -c 54 | - carthage archive RxKeyboard 55 | 56 | deploy: 57 | provider: releases 58 | api_key: $GITHUB_ACCESS_TOKEN 59 | file: $FRAMEWORK.framework.zip 60 | skip_cleanup: true 61 | on: 62 | repo: RxSwiftCommunity/RxKeyboard 63 | tags: true 64 | condition: $SDK = $IOS_SDK 65 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.3) 5 | activesupport (5.2.4.4) 6 | concurrent-ruby (~> 1.0, >= 1.0.2) 7 | i18n (>= 0.7, < 2) 8 | minitest (~> 5.1) 9 | tzinfo (~> 1.1) 10 | addressable (2.8.0) 11 | public_suffix (>= 2.0.2, < 5.0) 12 | algoliasearch (1.27.5) 13 | httpclient (~> 2.8, >= 2.8.3) 14 | json (>= 1.5.1) 15 | atomos (0.1.3) 16 | claide (1.0.3) 17 | cocoapods (1.10.0) 18 | addressable (~> 2.6) 19 | claide (>= 1.0.2, < 2.0) 20 | cocoapods-core (= 1.10.0) 21 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 22 | cocoapods-downloader (>= 1.4.0, < 2.0) 23 | cocoapods-plugins (>= 1.0.0, < 2.0) 24 | cocoapods-search (>= 1.0.0, < 2.0) 25 | cocoapods-trunk (>= 1.4.0, < 2.0) 26 | cocoapods-try (>= 1.1.0, < 2.0) 27 | colored2 (~> 3.1) 28 | escape (~> 0.0.4) 29 | fourflusher (>= 2.3.0, < 3.0) 30 | gh_inspector (~> 1.0) 31 | molinillo (~> 0.6.6) 32 | nap (~> 1.0) 33 | ruby-macho (~> 1.4) 34 | xcodeproj (>= 1.19.0, < 2.0) 35 | cocoapods-core (1.10.0) 36 | activesupport (> 5.0, < 6) 37 | addressable (~> 2.6) 38 | algoliasearch (~> 1.0) 39 | concurrent-ruby (~> 1.1) 40 | fuzzy_match (~> 2.0.4) 41 | nap (~> 1.0) 42 | netrc (~> 0.11) 43 | public_suffix 44 | typhoeus (~> 1.0) 45 | cocoapods-deintegrate (1.0.4) 46 | cocoapods-downloader (1.4.0) 47 | cocoapods-plugins (1.0.0) 48 | nap 49 | cocoapods-search (1.0.0) 50 | cocoapods-trunk (1.5.0) 51 | nap (>= 0.8, < 2.0) 52 | netrc (~> 0.11) 53 | cocoapods-try (1.2.0) 54 | colored2 (3.1.2) 55 | concurrent-ruby (1.1.7) 56 | escape (0.0.4) 57 | ethon (0.12.0) 58 | ffi (>= 1.3.0) 59 | ffi (1.14.2) 60 | fourflusher (2.3.1) 61 | fuzzy_match (2.0.4) 62 | gh_inspector (1.1.3) 63 | httpclient (2.8.3) 64 | i18n (1.8.6) 65 | concurrent-ruby (~> 1.0) 66 | json (2.5.1) 67 | minitest (5.14.2) 68 | molinillo (0.6.6) 69 | nanaimo (0.3.0) 70 | nap (1.1.0) 71 | netrc (0.11.0) 72 | public_suffix (4.0.6) 73 | ruby-macho (1.4.0) 74 | swiftproj (0.1.0) 75 | colored2 (>= 3.0) 76 | xcodeproj (>= 1.5) 77 | thread_safe (0.3.6) 78 | typhoeus (1.4.0) 79 | ethon (>= 0.9.0) 80 | tzinfo (1.2.9) 81 | thread_safe (~> 0.1) 82 | xcodeproj (1.19.0) 83 | CFPropertyList (>= 2.3.3, < 4.0) 84 | atomos (~> 0.1.3) 85 | claide (>= 1.0.2, < 2.0) 86 | colored2 (~> 3.1) 87 | nanaimo (~> 0.3.0) 88 | 89 | PLATFORMS 90 | ruby 91 | 92 | DEPENDENCIES 93 | cocoapods (~> 1.10) 94 | swiftproj 95 | 96 | BUNDLED WITH 97 | 2.2.4 98 | -------------------------------------------------------------------------------- /Example/Sources/Views/MessageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageCell.swift 3 | // RxKeyboard 4 | // 5 | // Created by Suyeol Jeon on 18/01/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import SwiftyImage 12 | 13 | final class MessageCell: UICollectionViewCell { 14 | 15 | // MARK: Types 16 | 17 | fileprivate enum BalloonAlignment { 18 | case left 19 | case right 20 | } 21 | 22 | 23 | // MARK: Constants 24 | 25 | struct Metric { 26 | static let maximumBalloonWidth = 240.f 27 | static let balloonViewInset = 10.f 28 | } 29 | 30 | struct Font { 31 | static let label = UIFont.systemFont(ofSize: 14) 32 | } 33 | 34 | 35 | // MARK: Properties 36 | 37 | fileprivate var balloonAlignment: BalloonAlignment = .left 38 | 39 | 40 | // MARK: UI 41 | 42 | fileprivate let otherBalloonViewImage = UIImage.resizable() 43 | .corner(radius: 5) 44 | .color(0xD9D9D9.color) 45 | .image 46 | fileprivate let myBalloonViewImage = UIImage.resizable() 47 | .corner(radius: 5) 48 | .color(0x1680FA.color) 49 | .image 50 | 51 | let balloonView = UIImageView() 52 | let label = UILabel().then { 53 | $0.font = Font.label 54 | $0.numberOfLines = 0 55 | } 56 | 57 | 58 | // MARK: Initializing 59 | 60 | override init(frame: CGRect) { 61 | super.init(frame: frame) 62 | self.contentView.addSubview(self.balloonView) 63 | self.contentView.addSubview(self.label) 64 | } 65 | 66 | required init?(coder aDecoder: NSCoder) { 67 | fatalError("init(coder:) has not been implemented") 68 | } 69 | 70 | 71 | // MARK: Configuring 72 | 73 | func configure(message: Message) { 74 | self.label.text = message.text 75 | 76 | switch message.user { 77 | case .other: 78 | self.balloonAlignment = .left 79 | self.balloonView.image = self.otherBalloonViewImage 80 | self.label.textColor = .black 81 | 82 | case .me: 83 | self.balloonAlignment = .right 84 | self.balloonView.image = self.myBalloonViewImage 85 | self.label.textColor = .white 86 | } 87 | 88 | self.setNeedsLayout() 89 | } 90 | 91 | 92 | // MARK: Size 93 | 94 | class func size(thatFitsWidth width: CGFloat, forMessage message: Message) -> CGSize { 95 | let labelWidth = Metric.maximumBalloonWidth - Metric.balloonViewInset * 2 96 | let constraintSize = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) 97 | let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading] 98 | let rect = message.text.boundingRect(with: constraintSize, 99 | options: options, 100 | attributes: [.font: Font.label], 101 | context: nil) 102 | let labelHeight = ceil(rect.height) 103 | return CGSize(width: width, height: labelHeight + Metric.balloonViewInset * 2) 104 | } 105 | 106 | 107 | // MARK: Layout 108 | 109 | override func layoutSubviews() { 110 | super.layoutSubviews() 111 | 112 | self.label.width = Metric.maximumBalloonWidth - Metric.balloonViewInset * 2 113 | self.label.sizeToFit() 114 | 115 | self.balloonView.width = self.label.width + Metric.balloonViewInset * 2 116 | self.balloonView.height = self.label.height + Metric.balloonViewInset * 2 117 | 118 | switch self.balloonAlignment { 119 | case .left: 120 | self.balloonView.left = 10 121 | case .right: 122 | self.balloonView.right = self.contentView.width - 10 123 | } 124 | 125 | self.label.top = self.balloonView.top + Metric.balloonViewInset 126 | self.label.left = self.balloonView.left + Metric.balloonViewInset 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /Example/Sources/Views/MessageInputBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageInputBar.swift 3 | // RxKeyboard 4 | // 5 | // Created by Suyeol Jeon on 18/01/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import RxCocoa 12 | import RxSwift 13 | import RxKeyboard 14 | 15 | final class MessageInputBar: UIView { 16 | 17 | // MARK: Properties 18 | 19 | private let disposeBag = DisposeBag() 20 | 21 | 22 | // MARK: UI 23 | 24 | let toolbar = UIToolbar() 25 | let textView = UITextView().then { 26 | $0.placeholder = "Say Hi!" 27 | $0.isEditable = true 28 | $0.showsVerticalScrollIndicator = false 29 | $0.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.6).cgColor 30 | $0.layer.borderWidth = 1 / UIScreen.main.scale 31 | $0.layer.cornerRadius = 3 32 | } 33 | let sendButton = UIButton(type: .system).then { 34 | $0.titleLabel?.font = UIFont.boldSystemFont(ofSize: UIFont.systemFontSize) 35 | $0.setTitle("Send", for: .normal) 36 | } 37 | 38 | 39 | // MARK: Initializing 40 | 41 | override init(frame: CGRect) { 42 | super.init(frame: frame) 43 | self.addSubview(self.toolbar) 44 | self.addSubview(self.textView) 45 | self.addSubview(self.sendButton) 46 | 47 | self.toolbar.snp.makeConstraints { make in 48 | make.edges.equalTo(0) 49 | } 50 | 51 | self.textView.snp.makeConstraints { make in 52 | make.top.left.equalTo(7) 53 | make.right.equalTo(self.sendButton.snp.left).offset(-7) 54 | make.bottom.equalTo(-7) 55 | } 56 | 57 | self.sendButton.snp.makeConstraints { make in 58 | make.top.equalTo(7) 59 | make.bottom.equalTo(-7) 60 | make.right.equalTo(-7) 61 | } 62 | 63 | self.textView.rx.text 64 | .map { text in text?.isEmpty == false } 65 | .bind(to: self.sendButton.rx.isEnabled) 66 | .disposed(by: self.disposeBag) 67 | 68 | RxKeyboard.instance.visibleHeight 69 | .map { $0 > 0 } 70 | .distinctUntilChanged().drive(onNext: { [weak self] (visible) in 71 | guard let `self` = self else { 72 | return 73 | } 74 | 75 | var bottomInset = 0.f 76 | if #available(iOS 11.0, *), !visible, let bottom = self.superview?.safeAreaInsets.bottom { 77 | bottomInset = bottom 78 | } 79 | 80 | self.toolbar.snp.remakeConstraints({ (make) in 81 | make.left.right.top.equalTo(0) 82 | make.bottom.equalTo(bottomInset) 83 | }) 84 | }) 85 | .disposed(by: self.disposeBag) 86 | } 87 | 88 | @available(iOS 11.0, *) 89 | override func safeAreaInsetsDidChange() { 90 | super.safeAreaInsetsDidChange() 91 | guard let bottomInset = self.superview?.safeAreaInsets.bottom else { 92 | return 93 | } 94 | 95 | self.toolbar.snp.remakeConstraints { make in 96 | make.top.left.right.equalTo(0) 97 | make.bottom.equalTo(bottomInset) 98 | } 99 | } 100 | 101 | required init?(coder aDecoder: NSCoder) { 102 | fatalError("init(coder:) has not been implemented") 103 | } 104 | 105 | 106 | // MARK: Size 107 | 108 | override var intrinsicContentSize: CGSize { 109 | return CGSize(width: self.width, height: 44) 110 | } 111 | 112 | } 113 | 114 | 115 | // MARK: - Reactive 116 | 117 | extension Reactive where Base: MessageInputBar { 118 | 119 | var sendButtonTap: ControlEvent { 120 | let source: Observable = self.base.sendButton.rx.tap 121 | .withLatestFrom(self.base.textView.rx.text.asObservable()) 122 | .flatMap { text -> Observable in 123 | if let text = text, !text.isEmpty { 124 | return .just(text) 125 | } else { 126 | return .empty() 127 | } 128 | } 129 | .do(onNext: { [weak base = self.base] _ in 130 | base?.textView.text = nil 131 | }) 132 | return ControlEvent(events: source) 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxKeyboard 2 | 3 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 4 | [![CocoaPods](http://img.shields.io/cocoapods/v/RxKeyboard.svg)](https://cocoapods.org/pods/RxKeyboard) 5 | [![Build Status](https://travis-ci.org/RxSwiftCommunity/RxKeyboard.svg?branch=master)](https://travis-ci.org/RxSwiftCommunity/RxKeyboard) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | 8 | RxKeyboard provides a reactive way of observing keyboard frame changes. Forget about keyboard notifications. It also perfectly works with `UIScrollViewKeyboardDismissMode.interactive`. 9 | 10 | ![rxkeyboard-message](https://cloud.githubusercontent.com/assets/931655/22062707/625eea7a-ddbe-11e6-9984-529abae1bd1a.gif) 11 | ![rxkeyboard-textview](https://cloud.githubusercontent.com/assets/931655/19223656/14bd915c-8eb0-11e6-93ea-7618fc9c5d81.gif) 12 | 13 | ## Getting Started 14 | 15 | RxKeyboard provides two [`Driver`](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#driver)s. 16 | 17 | ```swift 18 | /// An observable keyboard frame. 19 | let frame: Driver 20 | 21 | /// An observable visible height of keyboard. Emits keyboard height if the keyboard is visible 22 | /// or `0` if the keyboard is not visible. 23 | let visibleHeight: Driver 24 | 25 | /// Same with `visibleHeight` but only emits values when keyboard is about to show. This is 26 | /// useful when adjusting scroll view content offset. 27 | let willShowVisibleHeight: Driver 28 | ``` 29 | 30 | Use `RxKeyboard.instance` to get singleton instance. 31 | 32 | ```swift 33 | RxKeyboard.instance 34 | ``` 35 | 36 | Subscribe `RxKeyboard.instance.frame` to observe keyboard frame changes. 37 | 38 | ```swift 39 | RxKeyboard.instance.frame 40 | .drive(onNext: { frame in 41 | print(frame) 42 | }) 43 | .disposed(by: disposeBag) 44 | ``` 45 | 46 | ## Tips and Tricks 47 | 48 | - 🔗 **I want to adjust `UIScrollView`'s `contentInset` to fit keyboard height.** 49 | 50 | ```swift 51 | RxKeyboard.instance.visibleHeight 52 | .drive(onNext: { [scrollView] keyboardVisibleHeight in 53 | scrollView.contentInset.bottom = keyboardVisibleHeight 54 | }) 55 | .disposed(by: disposeBag) 56 | ``` 57 | 58 | - 🔗 **I want to adjust `UIScrollView`'s `contentOffset` to fit keyboard height.** 59 | 60 | ```swift 61 | RxKeyboard.instance.willShowVisibleHeight 62 | .drive(onNext: { [scrollView] keyboardVisibleHeight in 63 | scrollView.contentOffset.y += keyboardVisibleHeight 64 | }) 65 | .disposed(by: disposeBag) 66 | ``` 67 | 68 | - 🔗 **I want to make `UIToolbar` move along with the keyboard in an interactive dismiss mode. (Just like the wonderful GIF above!)** 69 | 70 | If you're not using Auto Layout: 71 | 72 | ```swift 73 | RxKeyboard.instance.visibleHeight 74 | .drive(onNext: { [toolbar, view] keyboardVisibleHeight in 75 | toolbar.frame.origin.y = view.frame.height - toolbar.frame.height - keyboardVisibleHeight 76 | }) 77 | .disposed(by: disposeBag) 78 | ``` 79 | 80 | If you're using Auto Layout, you have to capture the toolbar's bottom constraint and set `constant` to keyboard visible height. 81 | 82 | ```swift 83 | RxKeyboard.instance.visibleHeight 84 | .drive(onNext: { [toolbarBottomConstraint] keyboardVisibleHeight in 85 | toolbarBottomConstraint.constant = -1 * keyboardVisibleHeight 86 | }) 87 | .disposed(by: disposeBag) 88 | ``` 89 | 90 | > **Note**: In real world, you should use `setNeedsLayout()` and `layoutIfNeeded()` with animation block. See the [example project](https://github.com/RxSwiftCommunity/RxKeyboard/blob/master/Example/Sources/ViewControllers/MessageListViewController.swift#L92-L105) for example. 91 | 92 | - Anything else? Please open an issue or make a Pull Request. 93 | 94 | ## Dependencies 95 | 96 | - [RxSwift](https://github.com/ReactiveX/RxSwift) (>= 6.0) 97 | - [RxCocoa](https://github.com/ReactiveX/RxSwift) (>=6.0) 98 | 99 | ## Requirements 100 | 101 | - Swift 5.1 102 | - iOS 9+ 103 | 104 | ## Contributing 105 | 106 | In development, RxKeyboard manages dependencies with Swift Package Manager. Use the command below in order to generate a Xcode project file. Note that `.xcodeproj` file changes are not tracked via git. 107 | 108 | ```console 109 | $ swift package generate-xcodeproj 110 | ``` 111 | 112 | ## Installation 113 | 114 | - **Using [CocoaPods](https://cocoapods.org)**: 115 | 116 | ```ruby 117 | pod 'RxKeyboard' 118 | ``` 119 | 120 | - **Using [Carthage](https://github.com/Carthage/Carthage)**: 121 | 122 | ``` 123 | binary "https://raw.githubusercontent.com/RxSwiftCommunity/RxKeyboard/master/RxKeyboard.json" 124 | ``` 125 | 126 | ⚠️ With Carthage, RxKeyboard only supports binary installation: 127 | * 0.9.2 128 | * Xcode 10.1 (10B61) 129 | * Swift 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1) 130 | * 0.9.0 131 | * Xcode 10 (10A255) 132 | * Swift 4.2 (swiftlang-1000.11.37.1 clang-1000.11.45.1) 133 | * 0.8.2 134 | * Xcode 9.3 (9E145) 135 | * Swift 4.1.0 (swiftlang-902.0.48 clang-902.0.37.1) 136 | * 0.7.1 137 | * Xcode 9.1 (9B55) 138 | * Swift 4.0.2 (swiftlang-900.0.69.2 clang-900.0.38) 139 | * 0.7.0 140 | * 9.0.1 (9A1004) 141 | * Swift 4.0 (swiftlang-900.0.65.2 clang-900.0.37) 142 | 143 | ## License 144 | 145 | RxKeyboard is under MIT license. 146 | -------------------------------------------------------------------------------- /Sources/RxKeyboard/RxKeyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxKeyboard.swift 3 | // RxKeyboard 4 | // 5 | // Created by Suyeol Jeon on 09/10/2016. 6 | // Copyright © 2016 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | 12 | import RxCocoa 13 | import RxSwift 14 | 15 | public protocol RxKeyboardType { 16 | var frame: Driver { get } 17 | var visibleHeight: Driver { get } 18 | var willShowVisibleHeight: Driver { get } 19 | var isHidden: Driver { get } 20 | } 21 | 22 | /// RxKeyboard provides a reactive way of observing keyboard frame changes. 23 | public class RxKeyboard: NSObject, RxKeyboardType { 24 | 25 | // MARK: Public 26 | 27 | /// Get a singleton instance. 28 | public static let instance = RxKeyboard() 29 | 30 | /// An observable keyboard frame. 31 | public let frame: Driver 32 | 33 | /// An observable visible height of keyboard. Emits keyboard height if the keyboard is visible 34 | /// or `0` if the keyboard is not visible. 35 | public let visibleHeight: Driver 36 | 37 | /// Same with `visibleHeight` but only emits values when keyboard is about to show. This is 38 | /// useful when adjusting scroll view content offset. 39 | public let willShowVisibleHeight: Driver 40 | 41 | /// An observable visibility of keyboard. Emits keyboard visibility 42 | /// when changed keyboard show and hide. 43 | public let isHidden: Driver 44 | 45 | // MARK: Private 46 | 47 | private let disposeBag = DisposeBag() 48 | private let panRecognizer = UIPanGestureRecognizer() 49 | 50 | // MARK: Initializing 51 | 52 | override init() { 53 | 54 | let keyboardWillChangeFrame = UIResponder.keyboardWillChangeFrameNotification 55 | let keyboardWillHide = UIResponder.keyboardWillHideNotification 56 | let keyboardFrameEndKey = UIResponder.keyboardFrameEndUserInfoKey 57 | 58 | let defaultFrame = CGRect( 59 | x: 0, 60 | y: UIScreen.main.bounds.height, 61 | width: UIScreen.main.bounds.width, 62 | height: 0 63 | ) 64 | let frameVariable = BehaviorRelay(value: defaultFrame) 65 | self.frame = frameVariable.asDriver().distinctUntilChanged() 66 | self.visibleHeight = self.frame.map { UIScreen.main.bounds.intersection($0).height } 67 | self.willShowVisibleHeight = self.visibleHeight 68 | .scan((visibleHeight: 0, isShowing: false)) { lastState, newVisibleHeight in 69 | return (visibleHeight: newVisibleHeight, isShowing: lastState.visibleHeight == 0 && newVisibleHeight > 0) 70 | } 71 | .filter { state in state.isShowing } 72 | .map { state in state.visibleHeight } 73 | self.isHidden = self.visibleHeight.map({ $0 == 0.0 }).distinctUntilChanged() 74 | super.init() 75 | 76 | // keyboard will change frame 77 | let willChangeFrame = NotificationCenter.default.rx.notification(keyboardWillChangeFrame) 78 | .map { notification -> CGRect in 79 | let rectValue = notification.userInfo?[keyboardFrameEndKey] as? NSValue 80 | return rectValue?.cgRectValue ?? defaultFrame 81 | } 82 | .map { frame -> CGRect in 83 | if frame.origin.y < 0 { // if went to wrong frame 84 | var newFrame = frame 85 | newFrame.origin.y = UIScreen.main.bounds.height - newFrame.height 86 | return newFrame 87 | } 88 | return frame 89 | } 90 | 91 | // keyboard will hide 92 | let willHide = NotificationCenter.default.rx.notification(keyboardWillHide) 93 | .map { notification -> CGRect in 94 | let rectValue = notification.userInfo?[keyboardFrameEndKey] as? NSValue 95 | return rectValue?.cgRectValue ?? defaultFrame 96 | } 97 | .map { frame -> CGRect in 98 | if frame.origin.y < 0 { // if went to wrong frame 99 | var newFrame = frame 100 | newFrame.origin.y = UIScreen.main.bounds.height 101 | return newFrame 102 | } 103 | return frame 104 | } 105 | 106 | // pan gesture 107 | let didPan = self.panRecognizer.rx.event 108 | .withLatestFrom(frameVariable.asObservable()) { ($0, $1) } 109 | .flatMap { (gestureRecognizer, frame) -> Observable in 110 | guard case .changed = gestureRecognizer.state, 111 | let window = UIApplication.shared.windows.first, 112 | frame.origin.y < UIScreen.main.bounds.height 113 | else { return .empty() } 114 | let origin = gestureRecognizer.location(in: window) 115 | var newFrame = frame 116 | newFrame.origin.y = max(origin.y, UIScreen.main.bounds.height - frame.height) 117 | return .just(newFrame) 118 | } 119 | 120 | // merge into single sequence 121 | Observable.of(didPan, willChangeFrame, willHide).merge() 122 | .bind(to: frameVariable) 123 | .disposed(by: self.disposeBag) 124 | 125 | // gesture recognizer 126 | self.panRecognizer.delegate = self 127 | self.panRecognizer.maximumNumberOfTouches = 1 128 | 129 | UIApplication.rx.didFinishLaunching // when RxKeyboard is initialized before UIApplication.window is created 130 | .subscribe(onNext: { _ in 131 | UIApplication.shared.windows.first?.addGestureRecognizer(self.panRecognizer) 132 | }) 133 | .disposed(by: self.disposeBag) 134 | } 135 | 136 | } 137 | 138 | 139 | // MARK: - UIGestureRecognizerDelegate 140 | 141 | extension RxKeyboard: UIGestureRecognizerDelegate { 142 | 143 | public func gestureRecognizer( 144 | _ gestureRecognizer: UIGestureRecognizer, 145 | shouldReceive touch: UITouch 146 | ) -> Bool { 147 | let point = touch.location(in: gestureRecognizer.view) 148 | var view = gestureRecognizer.view?.hitTest(point, with: nil) 149 | while let candidate = view { 150 | if let scrollView = candidate as? UIScrollView, 151 | case .interactive = scrollView.keyboardDismissMode { 152 | return true 153 | } 154 | view = candidate.superview 155 | } 156 | return false 157 | } 158 | 159 | public func gestureRecognizer( 160 | _ gestureRecognizer: UIGestureRecognizer, 161 | shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer 162 | ) -> Bool { 163 | gestureRecognizer === self.panRecognizer 164 | } 165 | 166 | } 167 | #endif 168 | 169 | -------------------------------------------------------------------------------- /Example/Sources/ViewControllers/MessageListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageListViewController.swift 3 | // RxKeyboardExample 4 | // 5 | // Created by Suyeol Jeon on 09/10/2016. 6 | // Copyright © 2016 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import ReusableKit 12 | import RxKeyboard 13 | import RxSwift 14 | 15 | class MessageListViewController: UIViewController { 16 | 17 | // MARK: Constants 18 | 19 | struct Reusable { 20 | static let messageCell = ReusableCell() 21 | } 22 | 23 | 24 | // MARK: Properties 25 | 26 | private var didSetupViewConstraints = false 27 | private let disposeBag = DisposeBag() 28 | 29 | fileprivate var messages: [Message] = [ 30 | Message(user: .other, text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."), 31 | Message(user: .other, text: "Morbi et eros elementum, semper massa eu, pellentesque sapien."), 32 | Message(user: .me, text: "Aenean sollicitudin justo scelerisque tincidunt venenatis."), 33 | Message(user: .me, text: "Ut mollis magna nec interdum pellentesque."), 34 | Message(user: .me, text: "Aliquam semper nibh nec quam dapibus, a congue odio consequat."), 35 | Message(user: .other, text: "Nullam iaculis nisi in justo feugiat, at pharetra nulla dignissim."), 36 | Message(user: .me, text: "Fusce at nulla luctus, posuere mauris ut, viverra nunc."), 37 | Message(user: .other, text: "Nam feugiat urna non tortor ornare viverra."), 38 | Message(user: .other, text: "Donec vitae metus maximus, efficitur urna ac, blandit erat."), 39 | Message(user: .other, text: "Pellentesque luctus eros ac nisi ullamcorper pharetra nec vel felis."), 40 | Message(user: .me, text: "Duis vulputate magna quis urna porttitor, tempor malesuada metus volutpat."), 41 | Message(user: .me, text: "Duis aliquam urna quis metus tristique eleifend."), 42 | Message(user: .other, text: "Cras quis orci quis nisi vulputate mollis ut vitae magna."), 43 | Message(user: .other, text: "Fusce eu urna eu ipsum laoreet lobortis."), 44 | Message(user: .other, text: "Proin vitae tellus nec odio consequat varius ac non orci."), 45 | Message(user: .me, text: "Maecenas gravida arcu ut consectetur tincidunt."), 46 | Message(user: .me, text: "Quisque accumsan nisl ut ipsum rutrum, nec rutrum magna lobortis."), 47 | Message(user: .other, text: "Integer ac sem eu velit tincidunt hendrerit a in dui."), 48 | Message(user: .other, text: "Duis posuere arcu convallis tincidunt faucibus."), 49 | ] 50 | 51 | 52 | // MARK: UI 53 | 54 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()).then { 55 | $0.alwaysBounceVertical = true 56 | $0.keyboardDismissMode = .interactive 57 | $0.backgroundColor = .clear 58 | $0.register(Reusable.messageCell) 59 | ($0.collectionViewLayout as? UICollectionViewFlowLayout)?.do { 60 | $0.minimumLineSpacing = 6 61 | $0.sectionInset.top = 10 62 | $0.sectionInset.bottom = 10 63 | } 64 | } 65 | let messageInputBar = MessageInputBar() 66 | 67 | 68 | // MARK: Initializing 69 | 70 | init() { 71 | super.init(nibName: nil, bundle: nil) 72 | self.title = "RxKeyboard Example" 73 | } 74 | 75 | required init?(coder aDecoder: NSCoder) { 76 | fatalError("init(coder:) has not been implemented") 77 | } 78 | 79 | override func viewDidLoad() { 80 | super.viewDidLoad() 81 | self.view.addSubview(self.collectionView) 82 | self.view.addSubview(self.messageInputBar) 83 | 84 | self.collectionView.dataSource = self 85 | self.collectionView.delegate = self 86 | 87 | DispatchQueue.main.async { 88 | let indexPath = IndexPath(item: self.messages.count - 1, section: 0) 89 | self.collectionView.scrollToItem(at: indexPath, at: [], animated: true) 90 | } 91 | 92 | RxKeyboard.instance.visibleHeight 93 | .drive(onNext: { [weak self] keyboardVisibleHeight in 94 | guard let `self` = self, self.didSetupViewConstraints else { return } 95 | self.messageInputBar.snp.updateConstraints { make in 96 | if #available(iOS 11.0, *) { 97 | make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom).offset(-keyboardVisibleHeight) 98 | } else { 99 | make.bottom.equalTo(self.bottomLayoutGuide.snp.top).offset(-keyboardVisibleHeight) 100 | } 101 | } 102 | self.view.setNeedsLayout() 103 | UIView.animate(withDuration: 0) { 104 | self.collectionView.contentInset.bottom = keyboardVisibleHeight + self.messageInputBar.height 105 | self.collectionView.scrollIndicatorInsets.bottom = self.collectionView.contentInset.bottom 106 | self.view.layoutIfNeeded() 107 | } 108 | }) 109 | .disposed(by: self.disposeBag) 110 | 111 | RxKeyboard.instance.willShowVisibleHeight 112 | .drive(onNext: { keyboardVisibleHeight in 113 | self.collectionView.contentOffset.y += keyboardVisibleHeight 114 | }) 115 | .disposed(by: self.disposeBag) 116 | 117 | self.messageInputBar.rx.sendButtonTap 118 | .subscribe(onNext: { [weak self] text in 119 | guard let `self` = self else { return } 120 | let message = Message(user: .me, text: text) 121 | self.messages.append(message) 122 | let indexPath = IndexPath(item: self.messages.count - 1, section: 0) 123 | self.collectionView.insertItems(at: [indexPath]) 124 | self.collectionView.scrollToItem(at: indexPath, at: [], animated: true) 125 | }) 126 | .disposed(by: self.disposeBag) 127 | } 128 | 129 | 130 | // MARK: Auto Layout 131 | 132 | override func updateViewConstraints() { 133 | super.updateViewConstraints() 134 | guard !self.didSetupViewConstraints else { return } 135 | self.didSetupViewConstraints = true 136 | 137 | self.collectionView.snp.makeConstraints { make in 138 | make.edges.equalTo(0) 139 | } 140 | self.messageInputBar.snp.makeConstraints { make in 141 | make.left.right.equalTo(0) 142 | if #available(iOS 11.0, *) { 143 | make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) 144 | } else { 145 | make.bottom.equalTo(self.bottomLayoutGuide.snp.top) 146 | } 147 | } 148 | } 149 | 150 | override func viewDidLayoutSubviews() { 151 | super.viewDidLayoutSubviews() 152 | if self.collectionView.contentInset.bottom == 0 { 153 | self.collectionView.contentInset.bottom = self.messageInputBar.height 154 | self.collectionView.scrollIndicatorInsets.bottom = self.collectionView.contentInset.bottom 155 | } 156 | } 157 | 158 | } 159 | 160 | 161 | // MARK: - UICollectionViewDataSource 162 | 163 | extension MessageListViewController: UICollectionViewDataSource { 164 | 165 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 166 | return self.messages.count 167 | } 168 | 169 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 170 | let cell = collectionView.dequeue(Reusable.messageCell, for: indexPath) 171 | cell.configure(message: self.messages[indexPath.item]) 172 | return cell 173 | } 174 | 175 | } 176 | 177 | 178 | // MARK: - UICollectionViewDelegateFlowLayout 179 | 180 | extension MessageListViewController: UICollectionViewDelegateFlowLayout { 181 | 182 | func collectionView( 183 | _ collectionView: UICollectionView, 184 | layout collectionViewLayout: UICollectionViewLayout, 185 | sizeForItemAt indexPath: IndexPath 186 | ) -> CGSize { 187 | let message = self.messages[indexPath.item] 188 | return MessageCell.size(thatFitsWidth: collectionView.width, forMessage: message) 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /Example/RxKeyboardExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 032ABB8A1E35A9F200A3A5D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 032ABB7B1E35A9F200A3A5D4 /* Assets.xcassets */; }; 11 | 032ABB8B1E35A9F200A3A5D4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 032ABB7C1E35A9F200A3A5D4 /* LaunchScreen.storyboard */; }; 12 | 032ABB8C1E35A9F200A3A5D4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032ABB7F1E35A9F200A3A5D4 /* AppDelegate.swift */; }; 13 | 032ABB8D1E35A9F200A3A5D4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032ABB811E35A9F200A3A5D4 /* Message.swift */; }; 14 | 032ABB8E1E35A9F200A3A5D4 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032ABB821E35A9F200A3A5D4 /* User.swift */; }; 15 | 032ABB8F1E35A9F200A3A5D4 /* MessageListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032ABB841E35A9F200A3A5D4 /* MessageListViewController.swift */; }; 16 | 032ABB901E35A9F200A3A5D4 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032ABB861E35A9F200A3A5D4 /* MessageCell.swift */; }; 17 | 032ABB911E35A9F200A3A5D4 /* MessageInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032ABB871E35A9F200A3A5D4 /* MessageInputBar.swift */; }; 18 | B2F98E4D127A1E3F5A712D85 /* Pods_RxKeyboardExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DB8FDC47FB67FE136E7357D /* Pods_RxKeyboardExample.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 032ABB7B1E35A9F200A3A5D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 032ABB7D1E35A9F200A3A5D4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | 032ABB7F1E35A9F200A3A5D4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; tabWidth = 2; }; 25 | 032ABB811E35A9F200A3A5D4 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 26 | 032ABB821E35A9F200A3A5D4 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 27 | 032ABB841E35A9F200A3A5D4 /* MessageListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageListViewController.swift; sourceTree = ""; }; 28 | 032ABB861E35A9F200A3A5D4 /* MessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = ""; }; 29 | 032ABB871E35A9F200A3A5D4 /* MessageInputBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBar.swift; sourceTree = ""; }; 30 | 032ABB891E35A9F200A3A5D4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 03E8A71E1E35A95B00F7A3EC /* RxKeyboardRxKeyboardExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxKeyboardRxKeyboardExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 7DB8FDC47FB67FE136E7357D /* Pods_RxKeyboardExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxKeyboardExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | C2DC646E6858079F25D2BFF4 /* Pods-RxKeyboardExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxKeyboardExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxKeyboardExample/Pods-RxKeyboardExample.release.xcconfig"; sourceTree = ""; }; 34 | FF066ACB76646CE5837B517B /* Pods-RxKeyboardExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxKeyboardExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxKeyboardExample/Pods-RxKeyboardExample.debug.xcconfig"; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 03E8A71B1E35A95B00F7A3EC /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | B2F98E4D127A1E3F5A712D85 /* Pods_RxKeyboardExample.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 032ABB7A1E35A9F200A3A5D4 /* Resources */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 032ABB7B1E35A9F200A3A5D4 /* Assets.xcassets */, 53 | 032ABB7C1E35A9F200A3A5D4 /* LaunchScreen.storyboard */, 54 | ); 55 | path = Resources; 56 | sourceTree = ""; 57 | }; 58 | 032ABB7E1E35A9F200A3A5D4 /* Sources */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 032ABB7F1E35A9F200A3A5D4 /* AppDelegate.swift */, 62 | 032ABB801E35A9F200A3A5D4 /* Models */, 63 | 032ABB831E35A9F200A3A5D4 /* ViewControllers */, 64 | 032ABB851E35A9F200A3A5D4 /* Views */, 65 | ); 66 | path = Sources; 67 | sourceTree = ""; 68 | }; 69 | 032ABB801E35A9F200A3A5D4 /* Models */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 032ABB811E35A9F200A3A5D4 /* Message.swift */, 73 | 032ABB821E35A9F200A3A5D4 /* User.swift */, 74 | ); 75 | path = Models; 76 | sourceTree = ""; 77 | }; 78 | 032ABB831E35A9F200A3A5D4 /* ViewControllers */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 032ABB841E35A9F200A3A5D4 /* MessageListViewController.swift */, 82 | ); 83 | path = ViewControllers; 84 | sourceTree = ""; 85 | }; 86 | 032ABB851E35A9F200A3A5D4 /* Views */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 032ABB861E35A9F200A3A5D4 /* MessageCell.swift */, 90 | 032ABB871E35A9F200A3A5D4 /* MessageInputBar.swift */, 91 | ); 92 | path = Views; 93 | sourceTree = ""; 94 | }; 95 | 032ABB881E35A9F200A3A5D4 /* Supporting Files */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 032ABB891E35A9F200A3A5D4 /* Info.plist */, 99 | ); 100 | path = "Supporting Files"; 101 | sourceTree = ""; 102 | }; 103 | 03E8A7151E35A95B00F7A3EC = { 104 | isa = PBXGroup; 105 | children = ( 106 | 032ABB7E1E35A9F200A3A5D4 /* Sources */, 107 | 032ABB881E35A9F200A3A5D4 /* Supporting Files */, 108 | 032ABB7A1E35A9F200A3A5D4 /* Resources */, 109 | 03E8A71F1E35A95B00F7A3EC /* Products */, 110 | A5BB4D314040F3C02A825487 /* Pods */, 111 | 88C32DB01AFB62C918A6B8F0 /* Frameworks */, 112 | ); 113 | sourceTree = ""; 114 | }; 115 | 03E8A71F1E35A95B00F7A3EC /* Products */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 03E8A71E1E35A95B00F7A3EC /* RxKeyboardRxKeyboardExample.app */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 88C32DB01AFB62C918A6B8F0 /* Frameworks */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 7DB8FDC47FB67FE136E7357D /* Pods_RxKeyboardExample.framework */, 127 | ); 128 | name = Frameworks; 129 | sourceTree = ""; 130 | }; 131 | A5BB4D314040F3C02A825487 /* Pods */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | FF066ACB76646CE5837B517B /* Pods-RxKeyboardExample.debug.xcconfig */, 135 | C2DC646E6858079F25D2BFF4 /* Pods-RxKeyboardExample.release.xcconfig */, 136 | ); 137 | name = Pods; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | 03E8A71D1E35A95B00F7A3EC /* RxKeyboardExample */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 03E8A7301E35A95B00F7A3EC /* Build configuration list for PBXNativeTarget "RxKeyboardExample" */; 146 | buildPhases = ( 147 | D103E37D22DD7DDCE369FBBB /* [CP] Check Pods Manifest.lock */, 148 | 03E8A71A1E35A95B00F7A3EC /* Sources */, 149 | 03E8A71B1E35A95B00F7A3EC /* Frameworks */, 150 | 03E8A71C1E35A95B00F7A3EC /* Resources */, 151 | E857CEA8F03D723AC93BD7A1 /* [CP] Embed Pods Frameworks */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = RxKeyboardExample; 158 | productName = Example; 159 | productReference = 03E8A71E1E35A95B00F7A3EC /* RxKeyboardRxKeyboardExample.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | 03E8A7161E35A95B00F7A3EC /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastSwiftUpdateCheck = 0820; 169 | LastUpgradeCheck = 1020; 170 | ORGANIZATIONNAME = "Suyeol Jeon"; 171 | TargetAttributes = { 172 | 03E8A71D1E35A95B00F7A3EC = { 173 | CreatedOnToolsVersion = 8.2.1; 174 | ProvisioningStyle = Automatic; 175 | }; 176 | }; 177 | }; 178 | buildConfigurationList = 03E8A7191E35A95B00F7A3EC /* Build configuration list for PBXProject "RxKeyboardExample" */; 179 | compatibilityVersion = "Xcode 3.2"; 180 | developmentRegion = en; 181 | hasScannedForEncodings = 0; 182 | knownRegions = ( 183 | en, 184 | Base, 185 | ); 186 | mainGroup = 03E8A7151E35A95B00F7A3EC; 187 | productRefGroup = 03E8A71F1E35A95B00F7A3EC /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | 03E8A71D1E35A95B00F7A3EC /* RxKeyboardExample */, 192 | ); 193 | }; 194 | /* End PBXProject section */ 195 | 196 | /* Begin PBXResourcesBuildPhase section */ 197 | 03E8A71C1E35A95B00F7A3EC /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | 032ABB8A1E35A9F200A3A5D4 /* Assets.xcassets in Resources */, 202 | 032ABB8B1E35A9F200A3A5D4 /* LaunchScreen.storyboard in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXShellScriptBuildPhase section */ 209 | D103E37D22DD7DDCE369FBBB /* [CP] Check Pods Manifest.lock */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 216 | "${PODS_ROOT}/Manifest.lock", 217 | ); 218 | name = "[CP] Check Pods Manifest.lock"; 219 | outputPaths = ( 220 | "$(DERIVED_FILE_DIR)/Pods-RxKeyboardExample-checkManifestLockResult.txt", 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | shellPath = /bin/sh; 224 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 225 | showEnvVarsInLog = 0; 226 | }; 227 | E857CEA8F03D723AC93BD7A1 /* [CP] Embed Pods Frameworks */ = { 228 | isa = PBXShellScriptBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | inputPaths = ( 233 | "${PODS_ROOT}/Target Support Files/Pods-RxKeyboardExample/Pods-RxKeyboardExample-frameworks.sh", 234 | "${BUILT_PRODUCTS_DIR}/CGFloatLiteral/CGFloatLiteral.framework", 235 | "${BUILT_PRODUCTS_DIR}/ManualLayout/ManualLayout.framework", 236 | "${BUILT_PRODUCTS_DIR}/ReusableKit/ReusableKit.framework", 237 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 238 | "${BUILT_PRODUCTS_DIR}/RxKeyboard/RxKeyboard.framework", 239 | "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework", 240 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 241 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 242 | "${BUILT_PRODUCTS_DIR}/SwiftyColor/SwiftyColor.framework", 243 | "${BUILT_PRODUCTS_DIR}/SwiftyImage/SwiftyImage.framework", 244 | "${BUILT_PRODUCTS_DIR}/Then/Then.framework", 245 | "${BUILT_PRODUCTS_DIR}/UITextView+Placeholder/UITextView_Placeholder.framework", 246 | ); 247 | name = "[CP] Embed Pods Frameworks"; 248 | outputPaths = ( 249 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CGFloatLiteral.framework", 250 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ManualLayout.framework", 251 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReusableKit.framework", 252 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 253 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxKeyboard.framework", 254 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework", 255 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 256 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 257 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyColor.framework", 258 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyImage.framework", 259 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Then.framework", 260 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UITextView_Placeholder.framework", 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | shellPath = /bin/sh; 264 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RxKeyboardExample/Pods-RxKeyboardExample-frameworks.sh\"\n"; 265 | showEnvVarsInLog = 0; 266 | }; 267 | /* End PBXShellScriptBuildPhase section */ 268 | 269 | /* Begin PBXSourcesBuildPhase section */ 270 | 03E8A71A1E35A95B00F7A3EC /* Sources */ = { 271 | isa = PBXSourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 032ABB8C1E35A9F200A3A5D4 /* AppDelegate.swift in Sources */, 275 | 032ABB901E35A9F200A3A5D4 /* MessageCell.swift in Sources */, 276 | 032ABB8D1E35A9F200A3A5D4 /* Message.swift in Sources */, 277 | 032ABB8F1E35A9F200A3A5D4 /* MessageListViewController.swift in Sources */, 278 | 032ABB911E35A9F200A3A5D4 /* MessageInputBar.swift in Sources */, 279 | 032ABB8E1E35A9F200A3A5D4 /* User.swift in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXVariantGroup section */ 286 | 032ABB7C1E35A9F200A3A5D4 /* LaunchScreen.storyboard */ = { 287 | isa = PBXVariantGroup; 288 | children = ( 289 | 032ABB7D1E35A9F200A3A5D4 /* Base */, 290 | ); 291 | name = LaunchScreen.storyboard; 292 | sourceTree = ""; 293 | }; 294 | /* End PBXVariantGroup section */ 295 | 296 | /* Begin XCBuildConfiguration section */ 297 | 03E8A72E1E35A95B00F7A3EC /* Debug */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 304 | CLANG_CXX_LIBRARY = "libc++"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 323 | CLANG_WARN_STRICT_PROTOTYPES = YES; 324 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 328 | COPY_PHASE_STRIP = NO; 329 | DEBUG_INFORMATION_FORMAT = dwarf; 330 | ENABLE_STRICT_OBJC_MSGSEND = YES; 331 | ENABLE_TESTABILITY = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu99; 333 | GCC_DYNAMIC_NO_PIC = NO; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_OPTIMIZATION_LEVEL = 0; 336 | GCC_PREPROCESSOR_DEFINITIONS = ( 337 | "DEBUG=1", 338 | "$(inherited)", 339 | ); 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 347 | MTL_ENABLE_DEBUG_INFO = YES; 348 | ONLY_ACTIVE_ARCH = YES; 349 | SDKROOT = iphoneos; 350 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 351 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 352 | SWIFT_VERSION = 5.0; 353 | }; 354 | name = Debug; 355 | }; 356 | 03E8A72F1E35A95B00F7A3EC /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ALWAYS_SEARCH_USER_PATHS = NO; 360 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INFINITE_RECURSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 379 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 382 | CLANG_WARN_STRICT_PROTOTYPES = YES; 383 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 384 | CLANG_WARN_UNREACHABLE_CODE = YES; 385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 386 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 387 | COPY_PHASE_STRIP = NO; 388 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 389 | ENABLE_NS_ASSERTIONS = NO; 390 | ENABLE_STRICT_OBJC_MSGSEND = YES; 391 | GCC_C_LANGUAGE_STANDARD = gnu99; 392 | GCC_NO_COMMON_BLOCKS = YES; 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 400 | MTL_ENABLE_DEBUG_INFO = NO; 401 | SDKROOT = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | SWIFT_VERSION = 5.0; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 03E8A7311E35A95B00F7A3EC /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | baseConfigurationReference = FF066ACB76646CE5837B517B /* Pods-RxKeyboardExample.debug.xcconfig */; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist"; 414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 415 | PRODUCT_BUNDLE_IDENTIFIER = kr.xoul.RxKeyboardExample; 416 | PRODUCT_NAME = RxKeyboardRxKeyboardExample; 417 | }; 418 | name = Debug; 419 | }; 420 | 03E8A7321E35A95B00F7A3EC /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | baseConfigurationReference = C2DC646E6858079F25D2BFF4 /* Pods-RxKeyboardExample.release.xcconfig */; 423 | buildSettings = { 424 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 425 | INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist"; 426 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 427 | PRODUCT_BUNDLE_IDENTIFIER = kr.xoul.RxKeyboardExample; 428 | PRODUCT_NAME = RxKeyboardRxKeyboardExample; 429 | }; 430 | name = Release; 431 | }; 432 | /* End XCBuildConfiguration section */ 433 | 434 | /* Begin XCConfigurationList section */ 435 | 03E8A7191E35A95B00F7A3EC /* Build configuration list for PBXProject "RxKeyboardExample" */ = { 436 | isa = XCConfigurationList; 437 | buildConfigurations = ( 438 | 03E8A72E1E35A95B00F7A3EC /* Debug */, 439 | 03E8A72F1E35A95B00F7A3EC /* Release */, 440 | ); 441 | defaultConfigurationIsVisible = 0; 442 | defaultConfigurationName = Release; 443 | }; 444 | 03E8A7301E35A95B00F7A3EC /* Build configuration list for PBXNativeTarget "RxKeyboardExample" */ = { 445 | isa = XCConfigurationList; 446 | buildConfigurations = ( 447 | 03E8A7311E35A95B00F7A3EC /* Debug */, 448 | 03E8A7321E35A95B00F7A3EC /* Release */, 449 | ); 450 | defaultConfigurationIsVisible = 0; 451 | defaultConfigurationName = Release; 452 | }; 453 | /* End XCConfigurationList section */ 454 | }; 455 | rootObject = 03E8A7161E35A95B00F7A3EC /* Project object */; 456 | } 457 | --------------------------------------------------------------------------------