├── .gitignore ├── .swift-version ├── .travis.yml ├── Example ├── Podfile ├── Podfile.lock ├── ROCController.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── ROCController-Example.xcscheme ├── ROCController.xcworkspace │ └── contents.xcworkspacedata ├── ROCController │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── ChatHomeViewController.swift │ ├── ConversationsViewController.swift │ ├── DirectoryViewController.swift │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon-29x29@2x.png │ │ │ ├── AppIcon-29x29@3x.png │ │ │ ├── AppIcon-40x40@1x.png │ │ │ ├── AppIcon-40x40@2x-1.png │ │ │ ├── AppIcon-60x60@1x-1.png │ │ │ ├── AppIcon-60x60@2x-1.png │ │ │ ├── AppIcon-60x60@2x.png │ │ │ ├── AppIcon-60x60@3x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── launch_icon.imageset │ │ │ ├── Contents.json │ │ │ └── launch_icon@3x.png │ │ └── new_chat_icon.imageset │ │ │ ├── Contents.json │ │ │ └── new_chat_icon@3x.png │ ├── Info.plist │ ├── MinimalChatController.swift │ ├── MinimalChatMessage.swift │ ├── MinimalConversation.swift │ ├── MinimalConversationTableViewCell.swift │ ├── MinimalConversationsViewController.swift │ ├── ProfileEditViewController.swift │ ├── RecipientSelectorViewController.swift │ ├── RecipientTableViewCell.swift │ ├── SampleAppConstants.swift │ ├── ServerSetupViewController.swift │ ├── User.swift │ ├── UserDefaults+Extensions.swift │ └── WelcomeViewController.swift └── Tests │ ├── Info.plist │ └── Tests.swift ├── LICENSE ├── README.md ├── ROCController.podspec ├── ROCController ├── Assets │ ├── .gitkeep │ └── Images.xcassets │ │ ├── Contents.json │ │ ├── attach_icon.imageset │ │ ├── Contents.json │ │ └── attach_icon@3x.png │ │ └── send_icon.imageset │ │ ├── Contents.json │ │ └── send_icon@3x.png └── Classes │ ├── .gitkeep │ ├── ROCBaseChatMessage.swift │ ├── ROCBaseController.swift │ ├── ROCBaseMessageHandler.swift │ ├── ROCConfig.swift │ ├── ROCDataSource.swift │ ├── ROCDecorator.swift │ ├── ROCInputView.swift │ ├── ROCInternalImageGetter.swift │ ├── ROCMessageModelProtocol.swift │ ├── ROCMessageViewModelProtocol.swift │ ├── ROCNameSeparatorCellLayoutModel.swift │ ├── ROCNameSeparatorCollectionViewCell.swift │ ├── ROCNameSeparatorPresenter.swift │ ├── ROCNameSeparatorPresenterBuilder.swift │ ├── ROCNameSeperatorModel.swift │ ├── ROCTextMessageCollectionViewCellStyle.swift │ ├── ROCTextMessageHandler.swift │ ├── ROCTextMessageModel.swift │ ├── ROCTextMessageViewModel.swift │ ├── ROCTextMessageViewModelBuilder.swift │ ├── ROCTimeSeparatorCollectionViewCell.swift │ ├── ROCTimeSeparatorModel.swift │ ├── ROCTimeSeparatorPresenter.swift │ ├── ROCTimeSeparatorPresenterBuilder.swift │ ├── UIColor+Hex.swift │ └── Utils.swift └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Example 5 | Example/Pods 6 | 7 | # Xcode 8 | build/ 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata/ 18 | *.xccheckout 19 | profile 20 | *.moved-aside 21 | DerivedData 22 | *.hmap 23 | *.ipa 24 | 25 | # Bundler 26 | .bundle 27 | 28 | Carthage 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 32 | # 33 | # Note: if you ignore the Pods directory, make sure to uncomment 34 | # `pod install` in .travis.yml 35 | # 36 | # Pods/ 37 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -workspace Example/ROCController.xcworkspace -scheme ROCController-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | platform :ios, '10.0' 3 | 4 | target 'ROCController_Example' do 5 | pod 'ROCController', :path => '../' 6 | pod 'Fakery', '~> 2.0.0' 7 | pod 'Kingfisher', '~> 3.5.2' 8 | pod 'Eureka', '~> 3.0.0' 9 | pod 'TURecipientBar', '~> 2.0.4' 10 | 11 | target 'ROCController_Tests' do 12 | inherit! :search_paths 13 | 14 | pod 'Quick', '~> 1.0.0' 15 | pod 'Nimble', '~> 6.0.1' 16 | pod 'FBSnapshotTestCase', '~> 2.1.4' 17 | pod 'Nimble-Snapshots', '~> 4.4.0' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Chatto (3.1.0) 3 | - ChattoAdditions (3.1.0): 4 | - Chatto 5 | - Eureka (3.0.0) 6 | - Fakery (2.0.0) 7 | - FBSnapshotTestCase (2.1.4): 8 | - FBSnapshotTestCase/SwiftSupport (= 2.1.4) 9 | - FBSnapshotTestCase/Core (2.1.4) 10 | - FBSnapshotTestCase/SwiftSupport (2.1.4): 11 | - FBSnapshotTestCase/Core 12 | - Kingfisher (3.5.2) 13 | - Nimble (6.0.1) 14 | - Nimble-Snapshots (4.4.0): 15 | - Nimble-Snapshots/Core (= 4.4.0) 16 | - Nimble-Snapshots/Core (4.4.0): 17 | - FBSnapshotTestCase (~> 2.0) 18 | - Nimble 19 | - Quick 20 | - Quick (1.0.0) 21 | - Realm (2.8.1): 22 | - Realm/Headers (= 2.8.1) 23 | - Realm/Headers (2.8.1) 24 | - RealmSwift (2.8.1): 25 | - Realm (= 2.8.1) 26 | - ROCController (0.0.5): 27 | - Chatto (~> 3.1.0) 28 | - ChattoAdditions (~> 3.1.0) 29 | - RealmSwift (~> 2.8.1) 30 | - TURecipientBar (2.0.4) 31 | 32 | DEPENDENCIES: 33 | - Eureka (~> 3.0.0) 34 | - Fakery (~> 2.0.0) 35 | - FBSnapshotTestCase (~> 2.1.4) 36 | - Kingfisher (~> 3.5.2) 37 | - Nimble (~> 6.0.1) 38 | - Nimble-Snapshots (~> 4.4.0) 39 | - Quick (~> 1.0.0) 40 | - ROCController (from `../`) 41 | - TURecipientBar (~> 2.0.4) 42 | 43 | EXTERNAL SOURCES: 44 | ROCController: 45 | :path: ../ 46 | 47 | SPEC CHECKSUMS: 48 | Chatto: d959be2d284db62c713c68d7c39f2712ff902fa2 49 | ChattoAdditions: 64cfe2d3414a65d2d6bc8a1c5704df491eec28b6 50 | Eureka: f36fd440c3ebb7bfea0a4fbe6f16a05f1cb35906 51 | Fakery: 97b99c23937d2e025d9135c75e2b17351ccd5031 52 | FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a 53 | Kingfisher: 9950e382c500b6834ce004c7c4131c444e90c3a1 54 | Nimble: 1527fd1bd2b4cf0636251a36bc8ab37e81da8347 55 | Nimble-Snapshots: e743439f26c2fa99d8f7e0d7c01c99bcb40aa6f2 56 | Quick: 8024e4a47e6cc03a9d5245ef0948264fc6d27cff 57 | Realm: 2627602ad6818451f0cb8c2a6e072f7f10a5f360 58 | RealmSwift: 4764ca7657f2193c256fb032c0b123926f70dbcd 59 | ROCController: c9743456f4fdea19ced8e2600e4acbca7628c502 60 | TURecipientBar: 4904645f476ad056e17c9458fa9894552339e63a 61 | 62 | PODFILE CHECKSUM: fc1abee63727b99898c91a0a5ef088fbe35b3ed5 63 | 64 | COCOAPODS: 1.2.1 65 | -------------------------------------------------------------------------------- /Example/ROCController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 02E27DC9E2DF2BD9E86F95B2 /* Pods_ROCController_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F0FB577162A6235445EE2BA /* Pods_ROCController_Tests.framework */; }; 11 | 03A0E08BB52F04F049F11ACE /* Pods_ROCController_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F36C79ABCD485D771194A13 /* Pods_ROCController_Example.framework */; }; 12 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 13 | 607FACD81AFB9204008FA782 /* MinimalChatController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* MinimalChatController.swift */; }; 14 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 15 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 16 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 17 | E34B0DF21EF3210D0025ABAF /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34B0DF11EF3210D0025ABAF /* UserDefaults+Extensions.swift */; }; 18 | E34B0DF51EF3218B0025ABAF /* ServerSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34B0DF41EF3218B0025ABAF /* ServerSetupViewController.swift */; }; 19 | E3A917231E7C8F6400D4EE71 /* MinimalConversationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A917221E7C8F6400D4EE71 /* MinimalConversationsViewController.swift */; }; 20 | E3A917281E7C903200D4EE71 /* MinimalChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A917271E7C903200D4EE71 /* MinimalChatMessage.swift */; }; 21 | E3A9172A1E7C93E000D4EE71 /* MinimalConversation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A917291E7C93E000D4EE71 /* MinimalConversation.swift */; }; 22 | E3A9172C1E7C962700D4EE71 /* MinimalConversationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A9172B1E7C962700D4EE71 /* MinimalConversationTableViewCell.swift */; }; 23 | E3B131251E7D0EB700FABEE2 /* SampleAppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B131241E7D0EB700FABEE2 /* SampleAppConstants.swift */; }; 24 | E3C1210C1EF8529F008B6C30 /* ChatHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3C1210B1EF8529F008B6C30 /* ChatHomeViewController.swift */; }; 25 | E3D38D7C1EF99E57008E428F /* RecipientSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D38D7B1EF99E57008E428F /* RecipientSelectorViewController.swift */; }; 26 | E3D38D7F1EFA0E2A008E428F /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D38D7E1EFA0E2A008E428F /* User.swift */; }; 27 | E3D38D811EFAC1F0008E428F /* RecipientTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D38D801EFAC1F0008E428F /* RecipientTableViewCell.swift */; }; 28 | E3D38D851EFB1BCC008E428F /* ConversationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D38D841EFB1BCC008E428F /* ConversationsViewController.swift */; }; 29 | E3D38D881EFB1C73008E428F /* DirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D38D871EFB1C73008E428F /* DirectoryViewController.swift */; }; 30 | E3D38D8B1EFB1D15008E428F /* ProfileEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D38D8A1EFB1D15008E428F /* ProfileEditViewController.swift */; }; 31 | E3EB0CBE1E80814000BBC021 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EB0CBD1E80814000BBC021 /* WelcomeViewController.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 40 | remoteInfo = ROCController; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 3F36C79ABCD485D771194A13 /* Pods_ROCController_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ROCController_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 607FACD01AFB9204008FA782 /* ROCController_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ROCController_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 607FACD71AFB9204008FA782 /* MinimalChatController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimalChatController.swift; sourceTree = ""; }; 50 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 51 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 52 | 607FACE51AFB9204008FA782 /* ROCController_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ROCController_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 55 | 7F0FB577162A6235445EE2BA /* Pods_ROCController_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ROCController_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 8371081C1C4EA6EF68F0B4DA /* ROCController.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = ROCController.podspec; path = ../ROCController.podspec; sourceTree = ""; }; 57 | A77C9127BD286BC40DA53796 /* Pods-ROCController_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ROCController_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ROCController_Tests/Pods-ROCController_Tests.release.xcconfig"; sourceTree = ""; }; 58 | AE37B841EB291C7DF9DCBF6E /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 59 | AF7C77AEA5E97A85408307C4 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 60 | B14FAAC5762CA8C590AEA7EE /* Pods-ROCController_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ROCController_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-ROCController_Example/Pods-ROCController_Example.release.xcconfig"; sourceTree = ""; }; 61 | C674B8D90CEBA059199F635F /* Pods-ROCController_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ROCController_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ROCController_Example/Pods-ROCController_Example.debug.xcconfig"; sourceTree = ""; }; 62 | E0409C6444F690A5258736D8 /* Pods-ROCController_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ROCController_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ROCController_Tests/Pods-ROCController_Tests.debug.xcconfig"; sourceTree = ""; }; 63 | E34B0DF11EF3210D0025ABAF /* UserDefaults+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extensions.swift"; sourceTree = ""; }; 64 | E34B0DF41EF3218B0025ABAF /* ServerSetupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerSetupViewController.swift; sourceTree = ""; }; 65 | E3A917221E7C8F6400D4EE71 /* MinimalConversationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimalConversationsViewController.swift; sourceTree = ""; }; 66 | E3A917271E7C903200D4EE71 /* MinimalChatMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimalChatMessage.swift; sourceTree = ""; }; 67 | E3A917291E7C93E000D4EE71 /* MinimalConversation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimalConversation.swift; sourceTree = ""; }; 68 | E3A9172B1E7C962700D4EE71 /* MinimalConversationTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimalConversationTableViewCell.swift; sourceTree = ""; }; 69 | E3B131241E7D0EB700FABEE2 /* SampleAppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleAppConstants.swift; sourceTree = ""; }; 70 | E3C1210B1EF8529F008B6C30 /* ChatHomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHomeViewController.swift; sourceTree = ""; }; 71 | E3D38D7B1EF99E57008E428F /* RecipientSelectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipientSelectorViewController.swift; sourceTree = ""; }; 72 | E3D38D7E1EFA0E2A008E428F /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 73 | E3D38D801EFAC1F0008E428F /* RecipientTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipientTableViewCell.swift; sourceTree = ""; }; 74 | E3D38D841EFB1BCC008E428F /* ConversationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationsViewController.swift; sourceTree = ""; }; 75 | E3D38D871EFB1C73008E428F /* DirectoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryViewController.swift; sourceTree = ""; }; 76 | E3D38D8A1EFB1D15008E428F /* ProfileEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileEditViewController.swift; sourceTree = ""; }; 77 | E3EB0CBD1E80814000BBC021 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 03A0E08BB52F04F049F11ACE /* Pods_ROCController_Example.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | 02E27DC9E2DF2BD9E86F95B2 /* Pods_ROCController_Tests.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 607FACC71AFB9204008FA782 = { 101 | isa = PBXGroup; 102 | children = ( 103 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 104 | 607FACD21AFB9204008FA782 /* Example for ROCController */, 105 | 607FACE81AFB9204008FA782 /* Tests */, 106 | 607FACD11AFB9204008FA782 /* Products */, 107 | CC1BD190AA45E6C1B90FDCA5 /* Pods */, 108 | A1CA708ED8AEEEBF7CF0500C /* Frameworks */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | 607FACD11AFB9204008FA782 /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 607FACD01AFB9204008FA782 /* ROCController_Example.app */, 116 | 607FACE51AFB9204008FA782 /* ROCController_Tests.xctest */, 117 | ); 118 | name = Products; 119 | sourceTree = ""; 120 | }; 121 | 607FACD21AFB9204008FA782 /* Example for ROCController */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 125 | E3B131241E7D0EB700FABEE2 /* SampleAppConstants.swift */, 126 | E34B0DF11EF3210D0025ABAF /* UserDefaults+Extensions.swift */, 127 | E3A917241E7C8F6A00D4EE71 /* ViewControllers */, 128 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 129 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 130 | 607FACD31AFB9204008FA782 /* Supporting Files */, 131 | ); 132 | name = "Example for ROCController"; 133 | path = ROCController; 134 | sourceTree = ""; 135 | }; 136 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 607FACD41AFB9204008FA782 /* Info.plist */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | 607FACE81AFB9204008FA782 /* Tests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 148 | 607FACE91AFB9204008FA782 /* Supporting Files */, 149 | ); 150 | path = Tests; 151 | sourceTree = ""; 152 | }; 153 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 607FACEA1AFB9204008FA782 /* Info.plist */, 157 | ); 158 | name = "Supporting Files"; 159 | sourceTree = ""; 160 | }; 161 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 8371081C1C4EA6EF68F0B4DA /* ROCController.podspec */, 165 | AE37B841EB291C7DF9DCBF6E /* README.md */, 166 | AF7C77AEA5E97A85408307C4 /* LICENSE */, 167 | ); 168 | name = "Podspec Metadata"; 169 | sourceTree = ""; 170 | }; 171 | A1CA708ED8AEEEBF7CF0500C /* Frameworks */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 3F36C79ABCD485D771194A13 /* Pods_ROCController_Example.framework */, 175 | 7F0FB577162A6235445EE2BA /* Pods_ROCController_Tests.framework */, 176 | ); 177 | name = Frameworks; 178 | sourceTree = ""; 179 | }; 180 | CC1BD190AA45E6C1B90FDCA5 /* Pods */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | C674B8D90CEBA059199F635F /* Pods-ROCController_Example.debug.xcconfig */, 184 | B14FAAC5762CA8C590AEA7EE /* Pods-ROCController_Example.release.xcconfig */, 185 | E0409C6444F690A5258736D8 /* Pods-ROCController_Tests.debug.xcconfig */, 186 | A77C9127BD286BC40DA53796 /* Pods-ROCController_Tests.release.xcconfig */, 187 | ); 188 | name = Pods; 189 | sourceTree = ""; 190 | }; 191 | E34B0DF31EF321770025ABAF /* ServerSetup */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | E34B0DF41EF3218B0025ABAF /* ServerSetupViewController.swift */, 195 | ); 196 | name = ServerSetup; 197 | sourceTree = ""; 198 | }; 199 | E3A917241E7C8F6A00D4EE71 /* ViewControllers */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | E3D38D791EF99DBC008E428F /* Sync */, 203 | E3A917261E7C902200D4EE71 /* Minimal */, 204 | E3EB0CBC1E80813300BBC021 /* Welcome */, 205 | ); 206 | name = ViewControllers; 207 | sourceTree = ""; 208 | }; 209 | E3A917251E7C8FF900D4EE71 /* Conversations */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | E3A917221E7C8F6400D4EE71 /* MinimalConversationsViewController.swift */, 213 | E3A917291E7C93E000D4EE71 /* MinimalConversation.swift */, 214 | E3A9172B1E7C962700D4EE71 /* MinimalConversationTableViewCell.swift */, 215 | ); 216 | name = Conversations; 217 | sourceTree = ""; 218 | }; 219 | E3A917261E7C902200D4EE71 /* Minimal */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | E3A917251E7C8FF900D4EE71 /* Conversations */, 223 | 607FACD71AFB9204008FA782 /* MinimalChatController.swift */, 224 | E3A917271E7C903200D4EE71 /* MinimalChatMessage.swift */, 225 | ); 226 | name = Minimal; 227 | sourceTree = ""; 228 | }; 229 | E3C1210D1EF95E20008B6C30 /* ChatHome */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | E3D38D891EFB1D08008E428F /* ProfileEdit */, 233 | E3D38D861EFB1C63008E428F /* Directory */, 234 | E3D38D831EFB19AF008E428F /* Conversations */, 235 | E3C1210B1EF8529F008B6C30 /* ChatHomeViewController.swift */, 236 | ); 237 | name = ChatHome; 238 | sourceTree = ""; 239 | }; 240 | E3D38D791EF99DBC008E428F /* Sync */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | E3D38D7D1EFA0E23008E428F /* Models */, 244 | E3D38D7A1EF99E4A008E428F /* RecipientSelector */, 245 | E3C1210D1EF95E20008B6C30 /* ChatHome */, 246 | E34B0DF31EF321770025ABAF /* ServerSetup */, 247 | ); 248 | name = Sync; 249 | sourceTree = ""; 250 | }; 251 | E3D38D7A1EF99E4A008E428F /* RecipientSelector */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | E3D38D7B1EF99E57008E428F /* RecipientSelectorViewController.swift */, 255 | E3D38D801EFAC1F0008E428F /* RecipientTableViewCell.swift */, 256 | ); 257 | name = RecipientSelector; 258 | sourceTree = ""; 259 | }; 260 | E3D38D7D1EFA0E23008E428F /* Models */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | E3D38D7E1EFA0E2A008E428F /* User.swift */, 264 | ); 265 | name = Models; 266 | sourceTree = ""; 267 | }; 268 | E3D38D831EFB19AF008E428F /* Conversations */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | E3D38D841EFB1BCC008E428F /* ConversationsViewController.swift */, 272 | ); 273 | name = Conversations; 274 | sourceTree = ""; 275 | }; 276 | E3D38D861EFB1C63008E428F /* Directory */ = { 277 | isa = PBXGroup; 278 | children = ( 279 | E3D38D871EFB1C73008E428F /* DirectoryViewController.swift */, 280 | ); 281 | name = Directory; 282 | sourceTree = ""; 283 | }; 284 | E3D38D891EFB1D08008E428F /* ProfileEdit */ = { 285 | isa = PBXGroup; 286 | children = ( 287 | E3D38D8A1EFB1D15008E428F /* ProfileEditViewController.swift */, 288 | ); 289 | name = ProfileEdit; 290 | sourceTree = ""; 291 | }; 292 | E3EB0CBC1E80813300BBC021 /* Welcome */ = { 293 | isa = PBXGroup; 294 | children = ( 295 | E3EB0CBD1E80814000BBC021 /* WelcomeViewController.swift */, 296 | ); 297 | name = Welcome; 298 | sourceTree = ""; 299 | }; 300 | /* End PBXGroup section */ 301 | 302 | /* Begin PBXNativeTarget section */ 303 | 607FACCF1AFB9204008FA782 /* ROCController_Example */ = { 304 | isa = PBXNativeTarget; 305 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ROCController_Example" */; 306 | buildPhases = ( 307 | AF71CFE45D7ECF7061A6CE0B /* [CP] Check Pods Manifest.lock */, 308 | 607FACCC1AFB9204008FA782 /* Sources */, 309 | 607FACCD1AFB9204008FA782 /* Frameworks */, 310 | 607FACCE1AFB9204008FA782 /* Resources */, 311 | 63C6F1A49D3B3E47388BFD2F /* [CP] Embed Pods Frameworks */, 312 | 9B9E820C30AD2EDEA7E727D9 /* [CP] Copy Pods Resources */, 313 | ); 314 | buildRules = ( 315 | ); 316 | dependencies = ( 317 | ); 318 | name = ROCController_Example; 319 | productName = ROCController; 320 | productReference = 607FACD01AFB9204008FA782 /* ROCController_Example.app */; 321 | productType = "com.apple.product-type.application"; 322 | }; 323 | 607FACE41AFB9204008FA782 /* ROCController_Tests */ = { 324 | isa = PBXNativeTarget; 325 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ROCController_Tests" */; 326 | buildPhases = ( 327 | 1CF42619005E1F33ECFA45A2 /* [CP] Check Pods Manifest.lock */, 328 | 607FACE11AFB9204008FA782 /* Sources */, 329 | 607FACE21AFB9204008FA782 /* Frameworks */, 330 | 607FACE31AFB9204008FA782 /* Resources */, 331 | D53A770403AA24CA6821665B /* [CP] Embed Pods Frameworks */, 332 | 7EC76291814E444494117941 /* [CP] Copy Pods Resources */, 333 | ); 334 | buildRules = ( 335 | ); 336 | dependencies = ( 337 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 338 | ); 339 | name = ROCController_Tests; 340 | productName = Tests; 341 | productReference = 607FACE51AFB9204008FA782 /* ROCController_Tests.xctest */; 342 | productType = "com.apple.product-type.bundle.unit-test"; 343 | }; 344 | /* End PBXNativeTarget section */ 345 | 346 | /* Begin PBXProject section */ 347 | 607FACC81AFB9204008FA782 /* Project object */ = { 348 | isa = PBXProject; 349 | attributes = { 350 | LastSwiftUpdateCheck = 0720; 351 | LastUpgradeCheck = 0820; 352 | ORGANIZATIONNAME = CocoaPods; 353 | TargetAttributes = { 354 | 607FACCF1AFB9204008FA782 = { 355 | CreatedOnToolsVersion = 6.3.1; 356 | DevelopmentTeam = G47H383S4Y; 357 | LastSwiftMigration = 0820; 358 | }; 359 | 607FACE41AFB9204008FA782 = { 360 | CreatedOnToolsVersion = 6.3.1; 361 | DevelopmentTeam = A5KG55JAMM; 362 | LastSwiftMigration = 0820; 363 | TestTargetID = 607FACCF1AFB9204008FA782; 364 | }; 365 | }; 366 | }; 367 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ROCController" */; 368 | compatibilityVersion = "Xcode 3.2"; 369 | developmentRegion = English; 370 | hasScannedForEncodings = 0; 371 | knownRegions = ( 372 | en, 373 | Base, 374 | ); 375 | mainGroup = 607FACC71AFB9204008FA782; 376 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 377 | projectDirPath = ""; 378 | projectRoot = ""; 379 | targets = ( 380 | 607FACCF1AFB9204008FA782 /* ROCController_Example */, 381 | 607FACE41AFB9204008FA782 /* ROCController_Tests */, 382 | ); 383 | }; 384 | /* End PBXProject section */ 385 | 386 | /* Begin PBXResourcesBuildPhase section */ 387 | 607FACCE1AFB9204008FA782 /* Resources */ = { 388 | isa = PBXResourcesBuildPhase; 389 | buildActionMask = 2147483647; 390 | files = ( 391 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 392 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | }; 396 | 607FACE31AFB9204008FA782 /* Resources */ = { 397 | isa = PBXResourcesBuildPhase; 398 | buildActionMask = 2147483647; 399 | files = ( 400 | ); 401 | runOnlyForDeploymentPostprocessing = 0; 402 | }; 403 | /* End PBXResourcesBuildPhase section */ 404 | 405 | /* Begin PBXShellScriptBuildPhase section */ 406 | 1CF42619005E1F33ECFA45A2 /* [CP] Check Pods Manifest.lock */ = { 407 | isa = PBXShellScriptBuildPhase; 408 | buildActionMask = 2147483647; 409 | files = ( 410 | ); 411 | inputPaths = ( 412 | ); 413 | name = "[CP] Check Pods Manifest.lock"; 414 | outputPaths = ( 415 | ); 416 | runOnlyForDeploymentPostprocessing = 0; 417 | shellPath = /bin/sh; 418 | 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"; 419 | showEnvVarsInLog = 0; 420 | }; 421 | 63C6F1A49D3B3E47388BFD2F /* [CP] Embed Pods Frameworks */ = { 422 | isa = PBXShellScriptBuildPhase; 423 | buildActionMask = 2147483647; 424 | files = ( 425 | ); 426 | inputPaths = ( 427 | ); 428 | name = "[CP] Embed Pods Frameworks"; 429 | outputPaths = ( 430 | ); 431 | runOnlyForDeploymentPostprocessing = 0; 432 | shellPath = /bin/sh; 433 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ROCController_Example/Pods-ROCController_Example-frameworks.sh\"\n"; 434 | showEnvVarsInLog = 0; 435 | }; 436 | 7EC76291814E444494117941 /* [CP] Copy Pods Resources */ = { 437 | isa = PBXShellScriptBuildPhase; 438 | buildActionMask = 2147483647; 439 | files = ( 440 | ); 441 | inputPaths = ( 442 | ); 443 | name = "[CP] Copy Pods Resources"; 444 | outputPaths = ( 445 | ); 446 | runOnlyForDeploymentPostprocessing = 0; 447 | shellPath = /bin/sh; 448 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ROCController_Tests/Pods-ROCController_Tests-resources.sh\"\n"; 449 | showEnvVarsInLog = 0; 450 | }; 451 | 9B9E820C30AD2EDEA7E727D9 /* [CP] Copy Pods Resources */ = { 452 | isa = PBXShellScriptBuildPhase; 453 | buildActionMask = 2147483647; 454 | files = ( 455 | ); 456 | inputPaths = ( 457 | ); 458 | name = "[CP] Copy Pods Resources"; 459 | outputPaths = ( 460 | ); 461 | runOnlyForDeploymentPostprocessing = 0; 462 | shellPath = /bin/sh; 463 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ROCController_Example/Pods-ROCController_Example-resources.sh\"\n"; 464 | showEnvVarsInLog = 0; 465 | }; 466 | AF71CFE45D7ECF7061A6CE0B /* [CP] Check Pods Manifest.lock */ = { 467 | isa = PBXShellScriptBuildPhase; 468 | buildActionMask = 2147483647; 469 | files = ( 470 | ); 471 | inputPaths = ( 472 | ); 473 | name = "[CP] Check Pods Manifest.lock"; 474 | outputPaths = ( 475 | ); 476 | runOnlyForDeploymentPostprocessing = 0; 477 | shellPath = /bin/sh; 478 | 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"; 479 | showEnvVarsInLog = 0; 480 | }; 481 | D53A770403AA24CA6821665B /* [CP] Embed Pods Frameworks */ = { 482 | isa = PBXShellScriptBuildPhase; 483 | buildActionMask = 2147483647; 484 | files = ( 485 | ); 486 | inputPaths = ( 487 | ); 488 | name = "[CP] Embed Pods Frameworks"; 489 | outputPaths = ( 490 | ); 491 | runOnlyForDeploymentPostprocessing = 0; 492 | shellPath = /bin/sh; 493 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ROCController_Tests/Pods-ROCController_Tests-frameworks.sh\"\n"; 494 | showEnvVarsInLog = 0; 495 | }; 496 | /* End PBXShellScriptBuildPhase section */ 497 | 498 | /* Begin PBXSourcesBuildPhase section */ 499 | 607FACCC1AFB9204008FA782 /* Sources */ = { 500 | isa = PBXSourcesBuildPhase; 501 | buildActionMask = 2147483647; 502 | files = ( 503 | E3D38D7F1EFA0E2A008E428F /* User.swift in Sources */, 504 | E3A917231E7C8F6400D4EE71 /* MinimalConversationsViewController.swift in Sources */, 505 | E34B0DF51EF3218B0025ABAF /* ServerSetupViewController.swift in Sources */, 506 | E34B0DF21EF3210D0025ABAF /* UserDefaults+Extensions.swift in Sources */, 507 | E3D38D7C1EF99E57008E428F /* RecipientSelectorViewController.swift in Sources */, 508 | E3D38D811EFAC1F0008E428F /* RecipientTableViewCell.swift in Sources */, 509 | E3D38D851EFB1BCC008E428F /* ConversationsViewController.swift in Sources */, 510 | E3EB0CBE1E80814000BBC021 /* WelcomeViewController.swift in Sources */, 511 | E3A917281E7C903200D4EE71 /* MinimalChatMessage.swift in Sources */, 512 | E3B131251E7D0EB700FABEE2 /* SampleAppConstants.swift in Sources */, 513 | E3A9172C1E7C962700D4EE71 /* MinimalConversationTableViewCell.swift in Sources */, 514 | E3D38D8B1EFB1D15008E428F /* ProfileEditViewController.swift in Sources */, 515 | E3C1210C1EF8529F008B6C30 /* ChatHomeViewController.swift in Sources */, 516 | E3D38D881EFB1C73008E428F /* DirectoryViewController.swift in Sources */, 517 | 607FACD81AFB9204008FA782 /* MinimalChatController.swift in Sources */, 518 | E3A9172A1E7C93E000D4EE71 /* MinimalConversation.swift in Sources */, 519 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 520 | ); 521 | runOnlyForDeploymentPostprocessing = 0; 522 | }; 523 | 607FACE11AFB9204008FA782 /* Sources */ = { 524 | isa = PBXSourcesBuildPhase; 525 | buildActionMask = 2147483647; 526 | files = ( 527 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 528 | ); 529 | runOnlyForDeploymentPostprocessing = 0; 530 | }; 531 | /* End PBXSourcesBuildPhase section */ 532 | 533 | /* Begin PBXTargetDependency section */ 534 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 535 | isa = PBXTargetDependency; 536 | target = 607FACCF1AFB9204008FA782 /* ROCController_Example */; 537 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 538 | }; 539 | /* End PBXTargetDependency section */ 540 | 541 | /* Begin PBXVariantGroup section */ 542 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 543 | isa = PBXVariantGroup; 544 | children = ( 545 | 607FACDF1AFB9204008FA782 /* Base */, 546 | ); 547 | name = LaunchScreen.xib; 548 | sourceTree = ""; 549 | }; 550 | /* End PBXVariantGroup section */ 551 | 552 | /* Begin XCBuildConfiguration section */ 553 | 607FACED1AFB9204008FA782 /* Debug */ = { 554 | isa = XCBuildConfiguration; 555 | buildSettings = { 556 | ALWAYS_SEARCH_USER_PATHS = NO; 557 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 558 | CLANG_CXX_LIBRARY = "libc++"; 559 | CLANG_ENABLE_MODULES = YES; 560 | CLANG_ENABLE_OBJC_ARC = YES; 561 | CLANG_WARN_BOOL_CONVERSION = YES; 562 | CLANG_WARN_CONSTANT_CONVERSION = YES; 563 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 564 | CLANG_WARN_EMPTY_BODY = YES; 565 | CLANG_WARN_ENUM_CONVERSION = YES; 566 | CLANG_WARN_INFINITE_RECURSION = YES; 567 | CLANG_WARN_INT_CONVERSION = YES; 568 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 569 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 570 | CLANG_WARN_UNREACHABLE_CODE = YES; 571 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 572 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 573 | COPY_PHASE_STRIP = NO; 574 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 575 | ENABLE_STRICT_OBJC_MSGSEND = YES; 576 | ENABLE_TESTABILITY = YES; 577 | GCC_C_LANGUAGE_STANDARD = gnu99; 578 | GCC_DYNAMIC_NO_PIC = NO; 579 | GCC_NO_COMMON_BLOCKS = YES; 580 | GCC_OPTIMIZATION_LEVEL = 0; 581 | GCC_PREPROCESSOR_DEFINITIONS = ( 582 | "DEBUG=1", 583 | "$(inherited)", 584 | ); 585 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 586 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 587 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 588 | GCC_WARN_UNDECLARED_SELECTOR = YES; 589 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 590 | GCC_WARN_UNUSED_FUNCTION = YES; 591 | GCC_WARN_UNUSED_VARIABLE = YES; 592 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 593 | MTL_ENABLE_DEBUG_INFO = YES; 594 | ONLY_ACTIVE_ARCH = YES; 595 | SDKROOT = iphoneos; 596 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 597 | }; 598 | name = Debug; 599 | }; 600 | 607FACEE1AFB9204008FA782 /* Release */ = { 601 | isa = XCBuildConfiguration; 602 | buildSettings = { 603 | ALWAYS_SEARCH_USER_PATHS = NO; 604 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 605 | CLANG_CXX_LIBRARY = "libc++"; 606 | CLANG_ENABLE_MODULES = YES; 607 | CLANG_ENABLE_OBJC_ARC = YES; 608 | CLANG_WARN_BOOL_CONVERSION = YES; 609 | CLANG_WARN_CONSTANT_CONVERSION = YES; 610 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 611 | CLANG_WARN_EMPTY_BODY = YES; 612 | CLANG_WARN_ENUM_CONVERSION = YES; 613 | CLANG_WARN_INFINITE_RECURSION = YES; 614 | CLANG_WARN_INT_CONVERSION = YES; 615 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 616 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 617 | CLANG_WARN_UNREACHABLE_CODE = YES; 618 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 619 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 620 | COPY_PHASE_STRIP = NO; 621 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 622 | ENABLE_NS_ASSERTIONS = NO; 623 | ENABLE_STRICT_OBJC_MSGSEND = YES; 624 | GCC_C_LANGUAGE_STANDARD = gnu99; 625 | GCC_NO_COMMON_BLOCKS = YES; 626 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 627 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 628 | GCC_WARN_UNDECLARED_SELECTOR = YES; 629 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 630 | GCC_WARN_UNUSED_FUNCTION = YES; 631 | GCC_WARN_UNUSED_VARIABLE = YES; 632 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 633 | MTL_ENABLE_DEBUG_INFO = NO; 634 | SDKROOT = iphoneos; 635 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 636 | VALIDATE_PRODUCT = YES; 637 | }; 638 | name = Release; 639 | }; 640 | 607FACF01AFB9204008FA782 /* Debug */ = { 641 | isa = XCBuildConfiguration; 642 | baseConfigurationReference = C674B8D90CEBA059199F635F /* Pods-ROCController_Example.debug.xcconfig */; 643 | buildSettings = { 644 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 645 | DEVELOPMENT_TEAM = G47H383S4Y; 646 | INFOPLIST_FILE = ROCController/Info.plist; 647 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 648 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 649 | MODULE_NAME = ExampleApp; 650 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 651 | PRODUCT_NAME = "$(TARGET_NAME)"; 652 | SWIFT_VERSION = 3.0; 653 | }; 654 | name = Debug; 655 | }; 656 | 607FACF11AFB9204008FA782 /* Release */ = { 657 | isa = XCBuildConfiguration; 658 | baseConfigurationReference = B14FAAC5762CA8C590AEA7EE /* Pods-ROCController_Example.release.xcconfig */; 659 | buildSettings = { 660 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 661 | DEVELOPMENT_TEAM = G47H383S4Y; 662 | INFOPLIST_FILE = ROCController/Info.plist; 663 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 664 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 665 | MODULE_NAME = ExampleApp; 666 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 667 | PRODUCT_NAME = "$(TARGET_NAME)"; 668 | SWIFT_VERSION = 3.0; 669 | }; 670 | name = Release; 671 | }; 672 | 607FACF31AFB9204008FA782 /* Debug */ = { 673 | isa = XCBuildConfiguration; 674 | baseConfigurationReference = E0409C6444F690A5258736D8 /* Pods-ROCController_Tests.debug.xcconfig */; 675 | buildSettings = { 676 | DEVELOPMENT_TEAM = A5KG55JAMM; 677 | FRAMEWORK_SEARCH_PATHS = ( 678 | "$(SDKROOT)/Developer/Library/Frameworks", 679 | "$(inherited)", 680 | ); 681 | GCC_PREPROCESSOR_DEFINITIONS = ( 682 | "DEBUG=1", 683 | "$(inherited)", 684 | ); 685 | INFOPLIST_FILE = Tests/Info.plist; 686 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 687 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 688 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 689 | PRODUCT_NAME = "$(TARGET_NAME)"; 690 | SWIFT_VERSION = 3.0; 691 | }; 692 | name = Debug; 693 | }; 694 | 607FACF41AFB9204008FA782 /* Release */ = { 695 | isa = XCBuildConfiguration; 696 | baseConfigurationReference = A77C9127BD286BC40DA53796 /* Pods-ROCController_Tests.release.xcconfig */; 697 | buildSettings = { 698 | DEVELOPMENT_TEAM = A5KG55JAMM; 699 | FRAMEWORK_SEARCH_PATHS = ( 700 | "$(SDKROOT)/Developer/Library/Frameworks", 701 | "$(inherited)", 702 | ); 703 | INFOPLIST_FILE = Tests/Info.plist; 704 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 705 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 706 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 707 | PRODUCT_NAME = "$(TARGET_NAME)"; 708 | SWIFT_VERSION = 3.0; 709 | }; 710 | name = Release; 711 | }; 712 | /* End XCBuildConfiguration section */ 713 | 714 | /* Begin XCConfigurationList section */ 715 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ROCController" */ = { 716 | isa = XCConfigurationList; 717 | buildConfigurations = ( 718 | 607FACED1AFB9204008FA782 /* Debug */, 719 | 607FACEE1AFB9204008FA782 /* Release */, 720 | ); 721 | defaultConfigurationIsVisible = 0; 722 | defaultConfigurationName = Release; 723 | }; 724 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ROCController_Example" */ = { 725 | isa = XCConfigurationList; 726 | buildConfigurations = ( 727 | 607FACF01AFB9204008FA782 /* Debug */, 728 | 607FACF11AFB9204008FA782 /* Release */, 729 | ); 730 | defaultConfigurationIsVisible = 0; 731 | defaultConfigurationName = Release; 732 | }; 733 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ROCController_Tests" */ = { 734 | isa = XCConfigurationList; 735 | buildConfigurations = ( 736 | 607FACF31AFB9204008FA782 /* Debug */, 737 | 607FACF41AFB9204008FA782 /* Release */, 738 | ); 739 | defaultConfigurationIsVisible = 0; 740 | defaultConfigurationName = Release; 741 | }; 742 | /* End XCConfigurationList section */ 743 | }; 744 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 745 | } 746 | -------------------------------------------------------------------------------- /Example/ROCController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/ROCController.xcodeproj/xcshareddata/xcschemes/ROCController-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/ROCController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ROCController/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ROCController 4 | // 5 | // Created by mbalex99 on 03/17/2017. 6 | // Copyright (c) 2017 mbalex99. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | // Override point for customization after application launch. 20 | 21 | 22 | window = UIWindow(frame: UIScreen.main.bounds) 23 | window?.makeKeyAndVisible() 24 | window?.tintColor = SampleAppConstants.Colors.primaryColor //changes the tint color 25 | 26 | let firstController: UIViewController = SyncUser.current == nil ? WelcomeViewController() : ChatHomeViewController() 27 | let navigationController = UINavigationController(rootViewController: firstController) 28 | navigationController.navigationBar.isOpaque = true 29 | navigationController.navigationBar.isTranslucent = false 30 | 31 | window?.rootViewController = navigationController 32 | 33 | return true 34 | } 35 | 36 | func applicationWillResignActive(_ application: UIApplication) { 37 | // 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. 38 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 39 | } 40 | 41 | func applicationDidEnterBackground(_ application: UIApplication) { 42 | // 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. 43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 44 | } 45 | 46 | func applicationWillEnterForeground(_ application: UIApplication) { 47 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 48 | } 49 | 50 | func applicationDidBecomeActive(_ application: UIApplication) { 51 | // 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. 52 | } 53 | 54 | func applicationWillTerminate(_ application: UIApplication) { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /Example/ROCController/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/ROCController/ChatHomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatHomeViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/19/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | 12 | class ChatHomeViewController: UITabBarController, RecipientSelectorViewControllerDelegate { 13 | 14 | 15 | lazy var newBarButtonItem: UIBarButtonItem = { 16 | let newImage = UIImage(named: "new_chat_icon")?.withRenderingMode(.alwaysTemplate) 17 | let b = UIBarButtonItem(image: newImage, style: .plain, target: nil, action: nil) 18 | b.tintColor = SampleAppConstants.Colors.primaryColor 19 | return b 20 | }() 21 | 22 | lazy var logoutBarButtonItem: UIBarButtonItem = { 23 | let b = UIBarButtonItem(title: "Logout", style: .plain, target: nil, action: nil) 24 | b.tintColor = SampleAppConstants.Colors.primaryColor 25 | return b 26 | }() 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | view.backgroundColor = .white 31 | 32 | title = "Realm Sync Chat" 33 | 34 | navigationItem.rightBarButtonItem = newBarButtonItem 35 | navigationItem.leftBarButtonItem = logoutBarButtonItem 36 | 37 | logoutBarButtonItem.target = self 38 | logoutBarButtonItem.action = #selector(ChatHomeViewController.attemptLogout) 39 | 40 | newBarButtonItem.target = self 41 | newBarButtonItem.action = #selector(ChatHomeViewController.presentRecipientSelector) 42 | 43 | 44 | let conversationsViewController = ConversationsViewController() 45 | conversationsViewController.tabBarItem.title = "Conversations" 46 | 47 | let directoryViewController = DirectoryViewController() 48 | directoryViewController.tabBarItem.title = "Users" 49 | 50 | let profileEditViewController = ProfileEditViewController() 51 | profileEditViewController.tabBarItem.title = "Profile" 52 | 53 | 54 | 55 | 56 | setViewControllers([ 57 | conversationsViewController, 58 | directoryViewController, 59 | profileEditViewController 60 | ], animated: false) 61 | 62 | } 63 | 64 | func presentRecipientSelector(){ 65 | let recipientSelector = RecipientSelectorViewController() 66 | recipientSelector.delegate = self 67 | let navigationController = UINavigationController(rootViewController:recipientSelector) 68 | present(navigationController, animated: true, completion: nil) 69 | } 70 | 71 | func attemptLogout(){ 72 | let alert = UIAlertController(title: "Logout?", message: nil, preferredStyle: .alert) 73 | alert.addAction(UIAlertAction(title: "Yes, Logout", style: .destructive, handler: { [weak self] (_) in 74 | guard let `self` = self else { return } 75 | SyncUser.current?.logOut() 76 | self.navigationController?.setViewControllers([WelcomeViewController()], animated: true) 77 | })) 78 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 79 | present(alert, animated: true, completion: nil) 80 | } 81 | 82 | // RecipientSelectorViewControllerDelegate 83 | func didSelectUserIds(userIds: [String]) { 84 | 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Example/ROCController/ConversationsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConversationsViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/21/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ConversationsViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | view.backgroundColor = .red 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Example/ROCController/DirectoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DirectoryViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/21/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | 12 | class DirectoryViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 13 | 14 | lazy var tableView: UITableView = { 15 | let t = UITableView() 16 | t.register(RecipientTableViewCell.self, forCellReuseIdentifier: RecipientTableViewCell.REUSED_ID) 17 | t.rowHeight = RecipientTableViewCell.HEIGHT 18 | t.translatesAutoresizingMaskIntoConstraints = false 19 | return t 20 | }() 21 | 22 | var notificationToken: NotificationToken? = nil 23 | let users: Results = User.globalUsersRealm.objects(User.self) 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | view.backgroundColor = .white 28 | view.addSubview(tableView) 29 | let views: [String: Any] = [ 30 | "tableView": tableView, 31 | "topLayoutGuide": topLayoutGuide 32 | ] 33 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[tableView]-0-|", options: [], metrics: nil, views: views)) 34 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[topLayoutGuide]-0-[tableView]-0-|", options: [], metrics: nil, views: views)) 35 | 36 | 37 | tableView.dataSource = self 38 | tableView.delegate = self 39 | 40 | let realm = try! Realm() 41 | realm.invalidate() 42 | 43 | notificationToken = users.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in 44 | guard let tableView = self?.tableView else { return } 45 | switch changes { 46 | case .initial: 47 | // Results are now populated and can be accessed without blocking the UI 48 | tableView.reloadData() 49 | break 50 | case .update(_, let deletions, let insertions, let modifications): 51 | // Query results have changed, so apply them to the UITableView 52 | tableView.beginUpdates() 53 | tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), 54 | with: .automatic) 55 | tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), 56 | with: .automatic) 57 | tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), 58 | with: .automatic) 59 | tableView.endUpdates() 60 | break 61 | case .error(let error): 62 | // An error occurred while opening the Realm file on the background worker thread 63 | fatalError("\(error)") 64 | break 65 | } 66 | } 67 | } 68 | 69 | deinit { 70 | notificationToken?.stop() 71 | } 72 | 73 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 74 | return users.count 75 | } 76 | 77 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 78 | let cell = tableView.dequeueReusableCell(withIdentifier: RecipientTableViewCell.REUSED_ID, for: indexPath) as! RecipientTableViewCell 79 | cell.setupWithUser(user: users[indexPath.row]) 80 | return cell 81 | } 82 | 83 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 84 | tableView.deselectRow(at: indexPath, animated: true) 85 | let chatHomeViewController = (parent as? ChatHomeViewController) 86 | chatHomeViewController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 87 | chatHomeViewController?.navigationController?.pushViewController(UIViewController(), animated: true) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@1x-1.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x-1.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon-40x40@1x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon-60x60@1x-1.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon-29x29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "AppIcon-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "AppIcon-40x40@2x-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon-60x60@2x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "AppIcon-60x60@2x-1.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "AppIcon-60x60@3x.png", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/launch_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "launch_icon@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/launch_icon.imageset/launch_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/launch_icon.imageset/launch_icon@3x.png -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/new_chat_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "new_chat_icon@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/ROCController/Images.xcassets/new_chat_icon.imageset/new_chat_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/Example/ROCController/Images.xcassets/new_chat_icon.imageset/new_chat_icon@3x.png -------------------------------------------------------------------------------- /Example/ROCController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | RealmChat 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 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Example/ROCController/MinimalChatController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ROCController 4 | // 5 | // Created by mbalex99 on 03/17/2017. 6 | // Copyright (c) 2017 mbalex99. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ROCController 11 | import RealmSwift 12 | 13 | class MinimalChatController: ROCBaseController { 14 | 15 | let conversation: MinimalConversation 16 | 17 | init(conversation: MinimalConversation){ 18 | self.conversation = conversation 19 | let chatMessages = conversation.chatMessages.sorted(byKeyPath: "timestamp", ascending: true) 20 | super.init(results: chatMessages) 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | title = "Now Chatting" 30 | // Do any additional setup after loading the view, typically from a nib. 31 | } 32 | 33 | override func didReceiveMemoryWarning() { 34 | super.didReceiveMemoryWarning() 35 | // Dispose of any resources that can be recreated. 36 | } 37 | 38 | override func sendButtonDidTap(text: String) { 39 | let sampleChatMessage = MinimalChatMessage() 40 | sampleChatMessage.userId = SampleAppConstants.myUserId 41 | sampleChatMessage.text = text 42 | let realm = try! Realm() 43 | try! realm.write { 44 | conversation.chatMessages.append(sampleChatMessage) 45 | } 46 | } 47 | 48 | override func attachmentButtonDidTapped() { 49 | let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 50 | alertController.addAction(UIAlertAction(title: "Take a Picture", style: .default, handler: { [weak self] (_) in 51 | 52 | })) 53 | alertController.addAction(UIAlertAction(title: "Choose from Library", style: .default, handler: { [weak self] (_) in 54 | 55 | })) 56 | alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 57 | present(alertController, animated: true, completion: nil) 58 | } 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /Example/ROCController/MinimalChatMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleChatMessage.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import ROCController 12 | 13 | class MinimalChatMessage: ROCChatMessage { 14 | 15 | override var isIncoming: Bool { 16 | return userId != SampleAppConstants.myUserId 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Example/ROCController/MinimalConversation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Conversation.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | class MinimalConversation: Object { 13 | 14 | dynamic var conversationId: String = UUID().uuidString 15 | dynamic var timestamp: Date = Date() 16 | dynamic var summary: String = "" 17 | dynamic var displayName: String = "" 18 | dynamic var imageUrl: String? = nil 19 | 20 | let chatMessages = List() 21 | 22 | override static func primaryKey() -> String? { 23 | return "conversationId" 24 | } 25 | 26 | override static func ignoredProperties() -> [String] { 27 | return ["friendlyTimestampString"] 28 | } 29 | 30 | var friendlyTimestampString: String { 31 | let dateFormatter = DateFormatter() 32 | 33 | if NSCalendar.current.isDateInToday(timestamp){ 34 | dateFormatter.dateFormat = "" 35 | } else if NSCalendar.current.isDateInYesterday(timestamp) { 36 | return "Yesterday" 37 | } else { 38 | dateFormatter.dateFormat = "MM/dd" 39 | } 40 | return dateFormatter.string(from: timestamp) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/ROCController/MinimalConversationTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConversationTableViewCell.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class MinimalConversationTableViewCell: UITableViewCell { 13 | 14 | static let REUSE_ID = "MinimalConversationTableViewCell" 15 | static let HEIGHT: CGFloat = 80 16 | 17 | lazy var userImageView: UIImageView = { 18 | let i = UIImageView() 19 | i.layer.cornerRadius = 60 / 2 20 | i.layer.masksToBounds = true 21 | i.layer.borderColor = UIColor.lightGray.cgColor 22 | i.layer.borderWidth = 1.0 23 | i.translatesAutoresizingMaskIntoConstraints = false 24 | return i 25 | }() 26 | 27 | lazy var nameLabel: UILabel = { 28 | let l = UILabel() 29 | l.font = UIFont.boldSystemFont(ofSize: 16) 30 | l.lineBreakMode = .byTruncatingTail 31 | l.translatesAutoresizingMaskIntoConstraints = false 32 | return l 33 | }() 34 | 35 | lazy var summaryLabel: UILabel = { 36 | let l = UILabel() 37 | l.font = UIFont.systemFont(ofSize: 14) 38 | l.numberOfLines = 0 39 | l.translatesAutoresizingMaskIntoConstraints = false 40 | return l 41 | }() 42 | 43 | lazy var timeLabel: UILabel = { 44 | let l = UILabel() 45 | l.font = UIFont.systemFont(ofSize: 14) 46 | l.numberOfLines = 0 47 | l.translatesAutoresizingMaskIntoConstraints = false 48 | l.textAlignment = .right 49 | return l 50 | }() 51 | 52 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 53 | super.init(style: style, reuseIdentifier: reuseIdentifier) 54 | contentView.addSubview(userImageView) 55 | contentView.addSubview(nameLabel) 56 | contentView.addSubview(summaryLabel) 57 | contentView.addSubview(timeLabel) 58 | 59 | let views: [String: UIView] = [ 60 | "userImageView": userImageView, 61 | "nameLabel": nameLabel, 62 | "summaryLabel": summaryLabel, 63 | "timeLabel": timeLabel 64 | ] 65 | 66 | contentView.addConstraints( 67 | NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[userImageView(60)]-8-[nameLabel]-8-[timeLabel]-16-|", options: [], metrics: nil, views: views) 68 | ) 69 | 70 | contentView.addConstraints( 71 | NSLayoutConstraint.constraints(withVisualFormat: "H:[userImageView(60)]-8-[summaryLabel]-16-|", options: [], metrics: nil, views: views) 72 | ) 73 | 74 | contentView.addConstraints( 75 | NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[userImageView(60)]", options: [], metrics: nil, views: views) 76 | ) 77 | 78 | contentView.addConstraints( 79 | NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[nameLabel(25)]-8-[summaryLabel]-8-|", options: [], metrics: nil, views: views) 80 | ) 81 | 82 | contentView.addConstraints( 83 | NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[timeLabel(25)]", options: [], metrics: nil, views: views) 84 | ) 85 | 86 | } 87 | 88 | required init?(coder aDecoder: NSCoder) { 89 | fatalError("init(coder:) has not been implemented") 90 | } 91 | 92 | func setupWithConversation(conversation: MinimalConversation){ 93 | if let imageUrl = conversation.imageUrl { 94 | let url = URL(string: imageUrl) 95 | userImageView.kf.setImage(with: url, options: [ .transition(.fade(0.2)) ]) 96 | } 97 | nameLabel.text = conversation.displayName 98 | summaryLabel.text = conversation.summary 99 | timeLabel.text = conversation.friendlyTimestampString 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Example/ROCController/MinimalConversationsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConversationsViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ROCController 11 | import RealmSwift 12 | 13 | import Fakery // import just for demo purposes begin here 14 | 15 | class MinimalConversationsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 16 | 17 | lazy var tableView: UITableView = { 18 | let t = UITableView() 19 | t.delegate = self 20 | t.dataSource = self 21 | t.translatesAutoresizingMaskIntoConstraints = false 22 | t.register(MinimalConversationTableViewCell.self, forCellReuseIdentifier: MinimalConversationTableViewCell.REUSE_ID) 23 | return t 24 | }() 25 | 26 | var token: NotificationToken? 27 | let realm: Realm 28 | let conversations: Results 29 | 30 | init(){ 31 | self.realm = try! Realm() 32 | self.conversations = self.realm.objects(MinimalConversation.self).sorted(byKeyPath: "timestamp", ascending: false) 33 | super.init(nibName: nil, bundle: nil) 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | title = "My Conversations" 43 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 44 | 45 | view.addSubview(tableView) 46 | let views: [String: UIView] = [ 47 | "tableView": tableView 48 | ] 49 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[tableView]-0-|", options: [], metrics: nil, views: views)) 50 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[tableView]-0-|", options: [], metrics: nil, views: views)) 51 | 52 | //if no conversations are present lets just bootstrap 20 converations some for fun. 53 | if self.conversations.count == 0 { 54 | let faker = Faker() 55 | var conversationsToAdd = [MinimalConversation]() 56 | for i in 1...20 { 57 | let conversation = MinimalConversation() 58 | conversation.conversationId = "\(i)" 59 | conversation.displayName = "\(faker.name.firstName()) \(faker.name.lastName())" 60 | 61 | let imageUrl = faker.internet.image(width: 160, height: 160) 62 | let imageUrlWithRedundantQueryString = "\(imageUrl)?v=\(i)" 63 | 64 | conversation.imageUrl = imageUrlWithRedundantQueryString 65 | conversation.summary = faker.lorem.sentence() 66 | conversationsToAdd.append(conversation) 67 | } 68 | try! realm.write { 69 | realm.add(conversationsToAdd, update: true) 70 | } 71 | } 72 | 73 | token = self.conversations.addNotificationBlock({ [weak self] (changes) in 74 | guard let tableView = self?.tableView else { return } 75 | switch changes { 76 | case .initial: 77 | tableView.reloadData() 78 | break 79 | case .update(_, let deletions, let insertions, let modifications): 80 | // Query results have changed, so apply them to the UITableView 81 | tableView.beginUpdates() 82 | tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), 83 | with: .automatic) 84 | tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), 85 | with: .automatic) 86 | tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), 87 | with: .automatic) 88 | tableView.endUpdates() 89 | break 90 | case .error(let error): 91 | fatalError("\(error)") 92 | break 93 | } 94 | }) 95 | 96 | } 97 | 98 | deinit { 99 | token?.stop() 100 | } 101 | 102 | //UITableViewDataSource and UITableViewDelegate Methods 103 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 104 | return conversations.count 105 | } 106 | 107 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 108 | return MinimalConversationTableViewCell.HEIGHT 109 | } 110 | 111 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 112 | let cell = tableView.dequeueReusableCell(withIdentifier: MinimalConversationTableViewCell.REUSE_ID, for: indexPath) as! MinimalConversationTableViewCell 113 | let conversation = conversations[indexPath.row] 114 | cell.setupWithConversation(conversation: conversation) 115 | return cell 116 | } 117 | 118 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 119 | tableView.deselectRow(at: indexPath, animated: true) 120 | let conversation = conversations[indexPath.row] 121 | let chatViewController = MinimalChatController(conversation: conversation) 122 | navigationController?.pushViewController(chatViewController, animated: true) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /Example/ROCController/ProfileEditViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileEditViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/21/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Eureka 11 | 12 | 13 | class ProfileEditViewController: FormViewController { 14 | 15 | static let NAME = "USERNAME" 16 | static let EMAIL = "EMAIL" 17 | static let CONFIRM = "CONFIRM" 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | form 23 | +++ Section() 24 | <<< TextRow(ProfileEditViewController.NAME) { row in 25 | row.title = "Name:" 26 | } 27 | <<< TextRow(ProfileEditViewController.EMAIL) { row in 28 | row.title = "Email:" 29 | } 30 | +++ Section() 31 | <<< ButtonRow(ProfileEditViewController.CONFIRM) { row in 32 | row.title = "Save Profile" 33 | }.onCellSelection({ [weak self] (_, _) in 34 | self?.attemptSave() 35 | }) 36 | 37 | form.setValues([ 38 | ProfileEditViewController.NAME: User.getMe().name, 39 | ProfileEditViewController.EMAIL: User.getMe().email 40 | ]) 41 | 42 | } 43 | 44 | func attemptSave(){ 45 | let me = User.getMe() 46 | let newName = form.values()[ProfileEditViewController.NAME] as! String 47 | let newEmail = form.values()[ProfileEditViewController.EMAIL] as! String 48 | User.upsertUserInfo(username: me.username, name: newName, email: newEmail) 49 | 50 | let alert = UIAlertController(title: "Information Saved!", message: nil, preferredStyle: .alert) 51 | alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil)) 52 | present(alert, animated: true, completion: nil) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Example/ROCController/RecipientSelectorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipientSelectorViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/20/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | import TURecipientBar 12 | 13 | 14 | class UserRecipient : NSObject, NSCopying, TURecipientProtocol { 15 | 16 | public func copy(with zone: NSZone? = nil) -> Any { 17 | return UserRecipient(user: self.user) 18 | } 19 | 20 | let user: User 21 | 22 | var recipientTitle: String 23 | 24 | init(user: User){ 25 | self.user = user 26 | self.recipientTitle = user.name 27 | super.init() 28 | } 29 | 30 | } 31 | 32 | 33 | 34 | protocol RecipientSelectorViewControllerDelegate: class { 35 | func didSelectUserIds(userIds: [String]) 36 | } 37 | 38 | class RecipientSelectorViewController: UIViewController, TURecipientsBarDelegate, UITableViewDelegate, UITableViewDataSource { 39 | 40 | weak var delegate: RecipientSelectorViewControllerDelegate? 41 | 42 | lazy var recipientsBar: TURecipientsBar = { 43 | let r = TURecipientsBar() 44 | r.translatesAutoresizingMaskIntoConstraints = false 45 | return r 46 | }() 47 | 48 | lazy var tableView: UITableView = { 49 | let t = UITableView() 50 | t.keyboardDismissMode = .interactive 51 | t.translatesAutoresizingMaskIntoConstraints = false 52 | return t 53 | }() 54 | 55 | lazy var cancelBarButtonItem: UIBarButtonItem = { 56 | let b = UIBarButtonItem(title: "Cancel", style: .plain, target: nil, action: nil) 57 | return b 58 | }() 59 | 60 | lazy var doneBarButtonItem: UIBarButtonItem = { 61 | let b = UIBarButtonItem(title: "Chat!", style: .plain, target: nil, action: nil) 62 | return b 63 | }() 64 | 65 | var results: Results? 66 | 67 | override func viewDidLoad() { 68 | super.viewDidLoad() 69 | view.backgroundColor = UIColor.white 70 | view.addSubview(recipientsBar) 71 | view.addSubview(tableView) 72 | 73 | title = "Select Users" 74 | 75 | let views: [String: Any] = [ 76 | "recipientsBar": recipientsBar, 77 | "tableView": tableView, 78 | "topLayoutGuide": topLayoutGuide 79 | ] 80 | 81 | navigationItem.leftBarButtonItem = cancelBarButtonItem 82 | 83 | cancelBarButtonItem.target = self 84 | cancelBarButtonItem.action = #selector(RecipientSelectorViewController.cancelBarButtonDidTap) 85 | 86 | doneBarButtonItem.target = self 87 | doneBarButtonItem.action = #selector(RecipientSelectorViewController.doneBarButtonDidTap) 88 | 89 | recipientsBar.showsAddButton = false 90 | recipientsBar.recipientsBarDelegate = self 91 | 92 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[recipientsBar]-0-|", options: [], metrics: nil, views: views)) 93 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[tableView]-0-|", options: [], metrics: nil, views: views)) 94 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[topLayoutGuide]-0-[recipientsBar(>=40)]-0-[tableView]-0-|", options: [], metrics: nil, views: views)) 95 | 96 | tableView.register(RecipientTableViewCell.self, forCellReuseIdentifier: RecipientTableViewCell.REUSED_ID) 97 | tableView.rowHeight = RecipientTableViewCell.HEIGHT 98 | tableView.dataSource = self 99 | tableView.delegate = self 100 | } 101 | 102 | override func viewDidAppear(_ animated: Bool) { 103 | super.viewDidAppear(animated) 104 | recipientsBar.becomeFirstResponder() 105 | } 106 | 107 | func evaluateDoneButtonVisibility(){ 108 | let isEnabled = recipientsBar.recipients.count > 0 109 | navigationItem.rightBarButtonItem = isEnabled ? doneBarButtonItem : nil 110 | } 111 | 112 | func cancelBarButtonDidTap(){ 113 | self.dismiss(animated: true, completion: nil) 114 | } 115 | 116 | func doneBarButtonDidTap(){ 117 | self.dismiss(animated: true) { 118 | self.delegate?.didSelectUserIds(userIds: (self.recipientsBar.recipients as! [UserRecipient]).map({ $0.user._id })) 119 | } 120 | } 121 | 122 | // TURecipientsBarDelegate 123 | 124 | func recipientsBar(_ recipientsBar: TURecipientsBar, textDidChange searchText: String?) { 125 | guard let searchText = searchText else { 126 | self.results = nil 127 | tableView.reloadData() 128 | return 129 | } 130 | self.results = searchText == "" ? nil : User.searchForUsers(searchTerm: searchText) 131 | tableView.reloadData() 132 | } 133 | 134 | func recipientsBarReturnButtonClicked(_ recipientsBar: TURecipientsBar) { 135 | guard let user = self.results?.first else { return } 136 | attemptToAdd(user: user) 137 | } 138 | 139 | // UITableViewDelegate 140 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 141 | return results?.count ?? 0 142 | } 143 | 144 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 145 | guard let user = results?[indexPath.row] else { return UITableViewCell() } 146 | let cell = tableView.dequeueReusableCell(withIdentifier: RecipientTableViewCell.REUSED_ID, for: indexPath) as! RecipientTableViewCell 147 | cell.setupWithUser(user: user) 148 | return cell 149 | } 150 | 151 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 152 | tableView.deselectRow(at: indexPath, animated: true) 153 | guard let user = self.results?[indexPath.row] else { return } 154 | attemptToAdd(user: user) 155 | } 156 | 157 | func attemptToAdd(user: User){ 158 | let contains = (recipientsBar.recipients as! [UserRecipient]).contains { (userRecipient) -> Bool in 159 | return userRecipient.user._id == user._id 160 | } 161 | if(contains) { 162 | return 163 | } 164 | recipientsBar.addRecipient(UserRecipient(user: user)) 165 | recipientsBar.text = nil 166 | evaluateDoneButtonVisibility() 167 | } 168 | 169 | func attemptToRemove(userId: String){ 170 | for recipent in (recipientsBar.recipients as! [UserRecipient]) { 171 | if (recipent.user._id == userId) { 172 | recipientsBar.removeRecipient(recipent) 173 | } 174 | } 175 | evaluateDoneButtonVisibility() 176 | } 177 | 178 | 179 | } 180 | -------------------------------------------------------------------------------- /Example/ROCController/RecipientTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecipientTableViewCell.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/21/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RecipientTableViewCell: UITableViewCell { 12 | 13 | static let REUSED_ID = "RecipientTableViewCell" 14 | static let HEIGHT: CGFloat = 80 15 | 16 | lazy var nameLabel: UILabel = { 17 | let l = UILabel() 18 | l.numberOfLines = 2 19 | l.translatesAutoresizingMaskIntoConstraints = false 20 | return l 21 | }() 22 | 23 | lazy var userImageView: UIImageView = { 24 | let i = UIImageView() 25 | i.layer.cornerRadius = 60 / 2 26 | i.layer.masksToBounds = true 27 | i.layer.borderColor = UIColor.lightGray.cgColor 28 | i.layer.borderWidth = 1.0 29 | i.translatesAutoresizingMaskIntoConstraints = false 30 | return i 31 | }() 32 | 33 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 34 | super.init(style: style, reuseIdentifier: reuseIdentifier) 35 | contentView.addSubview(nameLabel) 36 | contentView.addSubview(userImageView) 37 | let views: [String: Any] = [ 38 | "nameLabel": nameLabel, 39 | "userImageView": userImageView 40 | ] 41 | contentView.addConstraints( 42 | NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[userImageView(60)]-8-[nameLabel]-16-|", options: [], metrics: nil, views: views) 43 | ) 44 | contentView.addConstraints( 45 | NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[nameLabel]-8-|", options: [], metrics: nil, views: views) 46 | ) 47 | contentView.addConstraints( 48 | NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[userImageView(60)]", options: [], metrics: nil, views: views) 49 | ) 50 | } 51 | 52 | required init?(coder aDecoder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | 56 | func setupWithUser(user: User){ 57 | let mutableAttributedString = NSMutableAttributedString() 58 | mutableAttributedString 59 | .append(NSAttributedString(string: "\(user.name)\n", attributes: [ 60 | NSFontAttributeName: UIFont.boldSystemFont(ofSize: 16) 61 | ])) 62 | 63 | mutableAttributedString 64 | .append(NSAttributedString(string: "\(user.email)", attributes: [ 65 | NSFontAttributeName: UIFont.systemFont(ofSize: 16) 66 | ])) 67 | 68 | nameLabel.attributedText = mutableAttributedString 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Example/ROCController/SampleAppConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleAppConstants.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct SampleAppConstants { 12 | 13 | static let myUserId = "max" 14 | static let syncRosHost = "45.55.173.122" 15 | static let syncRosPort = "9080" 16 | 17 | struct Colors { 18 | static var primaryColor = UIColor(colorLiteralRed: 89/255, green: 86/255, blue: 158/255, alpha: 1.0) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Example/ROCController/ServerSetupViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerSetupViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/15/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | import Eureka 12 | 13 | class ServerSetupViewController: FormViewController { 14 | 15 | 16 | static let SERVER_URL = "SERVER_URL" 17 | static let USERNAME = "USERNAME" 18 | static let PASSWORD = "PASSWORD" 19 | static let CONFIRM = "CONFIRM" 20 | 21 | lazy var confirmButton: ButtonRow = { 22 | let row = ButtonRow(ServerSetupViewController.CONFIRM) { row in 23 | row.title = "Login" 24 | } 25 | return row 26 | }() 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | view.backgroundColor = .white 31 | 32 | title = "Connect to Realm Object Server" 33 | 34 | form +++ Section() 35 | <<< TextRow(ServerSetupViewController.SERVER_URL) { row in 36 | row.title = "ROS URL:" 37 | row.placeholder = "Your http or https auth endpoint" 38 | } 39 | <<< TextRow(ServerSetupViewController.USERNAME) { row in 40 | row.title = "Username:" 41 | row.cell.textField.autocorrectionType = .no 42 | row.cell.textField.autocapitalizationType = .none 43 | }.onChange({ (row) in 44 | UserDefaults.standard.cachedUsername = row.value 45 | }) 46 | <<< PasswordRow(ServerSetupViewController.PASSWORD) { row in 47 | row.title = "Password:" 48 | row.cell.textField.autocorrectionType = .no 49 | row.cell.textField.autocapitalizationType = .none 50 | } 51 | +++ Section() 52 | <<< confirmButton 53 | .onCellSelection({ [weak self] (_, _) in 54 | guard let `self` = self else { return } 55 | let url = self.form.values()[ServerSetupViewController.SERVER_URL] as? String ?? "" 56 | let username = self.form.values()[ServerSetupViewController.USERNAME] as? String ?? "" 57 | let password = self.form.values()[ServerSetupViewController.PASSWORD] as? String ?? "" 58 | let authURL = URL(string: url.replacingOccurrences(of: "realm://", with: "http://"))! 59 | self.attemptAuth(authUrl: authURL, username: username, password: password, isRegister: false) 60 | }) 61 | 62 | form.setValues([ 63 | ServerSetupViewController.SERVER_URL: "http://\(SampleAppConstants.syncRosHost):\(SampleAppConstants.syncRosPort)", 64 | ServerSetupViewController.USERNAME: UserDefaults.standard.cachedUsername 65 | ]) 66 | 67 | } 68 | 69 | func attemptAuth(authUrl: URL, username: String, password: String, isRegister: Bool) { 70 | confirmButton.title = "One Moment..." 71 | confirmButton.disabled = Condition(booleanLiteral: true) 72 | confirmButton.evaluateDisabled() 73 | 74 | let creds = SyncCredentials.usernamePassword(username: username, password: password, register: isRegister) 75 | SyncUser.logIn(with: creds, server: authUrl) { [weak self] (user, error) in 76 | DispatchQueue.main.async { 77 | if let _ = user { 78 | User.upsertUserInfo(username: username, name: username, email: username) 79 | self?.navigationController?.setViewControllers([ChatHomeViewController()], animated: true) 80 | }else { 81 | if isRegister == false { 82 | self?.confirmButton.title = "Creating a New User..." 83 | self?.attemptAuth(authUrl: authUrl, username: username, password: password, isRegister: true) 84 | }else { 85 | self?.confirmButton.title = "Login" 86 | self?.confirmButton.disabled = Condition(booleanLiteral: false) 87 | self?.confirmButton.evaluateDisabled() 88 | 89 | let alert = UIAlertController(title: "Uh Oh", message: "We ran into an error...\(error?.localizedDescription ?? "")", preferredStyle: .alert) 90 | 91 | alert.addAction(UIAlertAction(title: "Okay", style: .cancel, handler: nil)) 92 | 93 | self?.present(alert, animated: true, completion: nil) 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /Example/ROCController/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/20/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import RealmSwift 10 | 11 | 12 | /** 13 | * This is different from a Realm SyncUser instance. 14 | * Consider it like an entry or a contact in a directory. 15 | */ 16 | class User : Object { 17 | 18 | dynamic var _id: String = UUID().uuidString 19 | dynamic var username: String = "" 20 | dynamic var email: String = "" 21 | dynamic var name: String = "" 22 | 23 | override static func primaryKey() -> String? { 24 | return "_id" 25 | } 26 | 27 | static var globalUsersRealm: Realm { 28 | let syncConfig = SyncConfiguration(user: SyncUser.current!, realmURL: URL(string: "realm://\(SampleAppConstants.syncRosHost):\(SampleAppConstants.syncRosPort)/users")!) 29 | let realm = Realm.Configuration(syncConfiguration: syncConfig) 30 | return try! Realm(configuration: realm) 31 | } 32 | 33 | static func upsertUserInfo(userId: String = SyncUser.current!.identity!, username: String, name: String, email: String) { 34 | let user = User() 35 | user._id = userId 36 | user.username = username 37 | user.name = name 38 | user.email = email 39 | let realm = User.globalUsersRealm 40 | try! realm.write { 41 | realm.add(user, update: true) 42 | } 43 | } 44 | 45 | static func searchForUsers(searchTerm: String = "") -> Results { 46 | let realm = User.globalUsersRealm 47 | let predicate = NSPredicate(format: "(username CONTAINS[cd] %@) OR (name CONTAINS[cd] %@) OR (email CONTAINS[cd] %@)", searchTerm, searchTerm, searchTerm) 48 | let result = realm.objects(User.self).filter(predicate) 49 | return result 50 | } 51 | 52 | static func getMe() -> User { 53 | let realm = User.globalUsersRealm 54 | let user = realm.object(ofType: User.self, forPrimaryKey: SyncUser.current!.identity) 55 | return user! 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Example/ROCController/UserDefaults+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults+Extensions.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 6/15/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UserDefaults { 12 | 13 | var realmURL: String? { 14 | get { 15 | return self.string(forKey: "realmURL") 16 | }set(val) { 17 | self.set(val, forKey: "realmURL") 18 | } 19 | } 20 | 21 | var cachedUsername: String? { 22 | get { 23 | return self.string(forKey: "cachedUsername") 24 | } set(val) { 25 | self.set(val, forKey: "cachedUsername") 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Example/ROCController/WelcomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeViewController.swift 3 | // ROCController 4 | // 5 | // Created by Maximilian Alexander on 3/20/17. 6 | // Copyright © 2017 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | 12 | class WelcomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 13 | 14 | let options = [ 15 | "Minimal Example", 16 | "Sync Example" 17 | ] 18 | 19 | lazy var tableView: UITableView = { 20 | let t = UITableView() 21 | t.delegate = self 22 | t.dataSource = self 23 | t.translatesAutoresizingMaskIntoConstraints = false 24 | return t 25 | }() 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | title = "Realm Object Chat!" 30 | view.addSubview(tableView) 31 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[tableView]-0-|", options: [], metrics: nil, views: [ 32 | "tableView": tableView 33 | ])) 34 | 35 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[tableView]-0-|", options: [], metrics: nil, views: [ 36 | "tableView": tableView 37 | ])) 38 | 39 | } 40 | 41 | func numberOfSections(in tableView: UITableView) -> Int { 42 | return 1 43 | } 44 | 45 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 46 | return options.count 47 | } 48 | 49 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 50 | let cell = UITableViewCell() 51 | cell.textLabel?.text = options[indexPath.row] 52 | return cell 53 | } 54 | 55 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 56 | tableView.deselectRow(at: indexPath, animated: true) 57 | switch indexPath.row { 58 | case 0: 59 | let conversations = MinimalConversationsViewController() 60 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 61 | navigationController?.pushViewController(conversations, animated: true) 62 | break 63 | case 1: 64 | navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 65 | if SyncUser.current != nil { 66 | let home = ChatHomeViewController() 67 | navigationController?.pushViewController(home, animated: true) 68 | }else { 69 | let setup = ServerSetupViewController() 70 | navigationController?.pushViewController(setup, animated: true) 71 | } 72 | 73 | break 74 | default: 75 | break; 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Example/Tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | // https://github.com/Quick/Quick 2 | 3 | import Quick 4 | import Nimble 5 | import ROCController 6 | 7 | class TableOfContentsSpec: QuickSpec { 8 | override func spec() { 9 | describe("these will fail") { 10 | 11 | it("can do maths") { 12 | expect(1) == 2 13 | } 14 | 15 | it("can read") { 16 | expect("number") == "string" 17 | } 18 | 19 | it("will eventually fail") { 20 | expect("time").toEventually( equal("done") ) 21 | } 22 | 23 | context("these will pass") { 24 | 25 | it("can do maths") { 26 | expect(23) == 23 27 | } 28 | 29 | it("can read") { 30 | expect("🐮") == "🐮" 31 | } 32 | 33 | it("will eventually pass") { 34 | var time = "passing" 35 | 36 | DispatchQueue.main.async { 37 | time = "done" 38 | } 39 | 40 | waitUntil { done in 41 | Thread.sleep(forTimeInterval: 0.5) 42 | expect(time) == "done" 43 | 44 | done() 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 mbalex99 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ROCController 2 | 3 | [![CI Status](http://img.shields.io/travis/mbalex99/ROCController.svg?style=flat)](https://travis-ci.org/mbalex99/ROCController) 4 | [![Version](https://img.shields.io/cocoapods/v/ROCController.svg?style=flat)](http://cocoapods.org/pods/ROCController) 5 | [![License](https://img.shields.io/cocoapods/l/ROCController.svg?style=flat)](http://cocoapods.org/pods/ROCController) 6 | [![Platform](https://img.shields.io/cocoapods/p/ROCController.svg?style=flat)](http://cocoapods.org/pods/ROCController) 7 | 8 | ## Example 9 | 10 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 11 | 12 | ## Requirements 13 | 14 | ## Installation 15 | 16 | ROCController is available through [CocoaPods](http://cocoapods.org). To install 17 | it, simply add the following line to your Podfile: 18 | 19 | ```ruby 20 | pod "ROCController" 21 | ``` 22 | 23 | ## Tutorial 24 | 25 | 1. Whenever you need ROCController just make sure you `import ROCController` 26 | 2. If you need a special sort of ChatMessage, you can subclass `RealmChatMessage`. But you're free to use it right out of the box 27 | 3. Subclass `ROCBaseController` 28 | 4. Implement your own "send message". See `SampleChatController` how it overrides the "sendMessage 29 | 30 | ## FAQ 31 | 32 | ### The example project has a few different depedencies. Do I need them? 33 | No absolutely not, they're just to show that you can use a whole lot of other code with ROCController with minimal intrusion. 34 | 35 | 36 | ## Author 37 | **Maximilian Alexander** 38 | 39 | Contact: 40 | * @mbalex99 41 | * mbalex99@gmail.com 42 | * max.alexander@realm.io 43 | 44 | ## License 45 | 46 | ROCController is available under the MIT license. See the LICENSE file for more info. 47 | -------------------------------------------------------------------------------- /ROCController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint ROCController.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'ROCController' 11 | s.version = '0.0.5' 12 | s.summary = 'Realm Object Chat Controller. A powerful Chatto controller backed by Realm!' 13 | 14 | s.description = <<-DESC 15 | Creating a chat app is insanely hard. Not only the UI but also the data layer. With Realm's fast database and change notification API, we can show blazing fast, 60fps chats that you can easily integrate into your own app. 16 | DESC 17 | 18 | s.homepage = 'https://github.com/realm/roc-ios-controller' 19 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 20 | s.license = { :type => 'MIT', :file => 'LICENSE' } 21 | s.author = { 'mbalex99' => 'mbalex99@gmail.com' } 22 | s.source = { :git => 'https://github.com/realm/roc-ios-controller.git', :tag => s.version.to_s } 23 | s.social_media_url = 'https://twitter.com/maxofeden' 24 | 25 | s.ios.deployment_target = '10.0' 26 | 27 | s.source_files = 'ROCController/Classes/**/*' 28 | s.resources = 'ROCController/Assets/*.xcassets' 29 | 30 | # s.public_header_files = 'Pod/Classes/**/*.h' 31 | s.frameworks = 'UIKit' 32 | s.dependency 'RealmSwift', '~> 2.8.1' 33 | s.dependency 'Chatto', '~> 3.1.0' 34 | s.dependency 'ChattoAdditions', '~> 3.1.0' 35 | end 36 | -------------------------------------------------------------------------------- /ROCController/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/ROCController/Assets/.gitkeep -------------------------------------------------------------------------------- /ROCController/Assets/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ROCController/Assets/Images.xcassets/attach_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "attach_icon@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ROCController/Assets/Images.xcassets/attach_icon.imageset/attach_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/ROCController/Assets/Images.xcassets/attach_icon.imageset/attach_icon@3x.png -------------------------------------------------------------------------------- /ROCController/Assets/Images.xcassets/send_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "send_icon@3x.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ROCController/Assets/Images.xcassets/send_icon.imageset/send_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/ROCController/Assets/Images.xcassets/send_icon.imageset/send_icon@3x.png -------------------------------------------------------------------------------- /ROCController/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realm/roc-ios-controller/edebc514c80162ffa1d7666b62f59bae7dca899f/ROCController/Classes/.gitkeep -------------------------------------------------------------------------------- /ROCController/Classes/ROCBaseChatMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseChatMessage.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import Chatto 12 | import ChattoAdditions 13 | 14 | open class ROCChatMessage: Object { 15 | 16 | public dynamic var chatMessageId: String = UUID().uuidString 17 | public dynamic var timestamp: Date = Date() 18 | public dynamic var userId: String = UUID().uuidString 19 | public dynamic var userDisplayName: String = "" 20 | 21 | public dynamic var text: String = "" 22 | public dynamic var mimeType: String = "text/plain" 23 | 24 | /** 25 | * You can store JSON or any blob data here. 26 | */ 27 | public dynamic var binaryData: NSData? = nil 28 | 29 | override open static func primaryKey() -> String? { 30 | return "chatMessageId" 31 | } 32 | 33 | override open static func indexedProperties() -> [String] { 34 | return ["timestamp"] 35 | } 36 | 37 | } 38 | 39 | extension ROCChatMessage: ROCMessageModelProtocol { 40 | 41 | public var uid: String { 42 | return chatMessageId 43 | } 44 | 45 | public var type: ChatItemType { 46 | return mimeType 47 | } 48 | 49 | public var date: Date { 50 | return timestamp as Date 51 | } 52 | 53 | public var senderId: String { 54 | return userId 55 | } 56 | 57 | open var isIncoming : Bool { 58 | return true 59 | } 60 | 61 | public var status: MessageStatus { 62 | return .success 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCBaseController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCController.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | import ChattoAdditions 12 | import RealmSwift 13 | 14 | open class ROCBaseController: BaseChatViewController, ROCInputViewDelegate { 15 | 16 | open var messageHandler = ROCBaseMessageHandler() 17 | 18 | public init(results: Results){ 19 | super.init(nibName: nil, bundle: nil) 20 | self.chatDataSource = ROCDataSource(results: results) 21 | self.chatItemsDecorator = ROCDecorator() 22 | } 23 | 24 | required public init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | 29 | open override func viewDidLoad() { 30 | super.viewDidLoad() 31 | (self.chatDataSource as! ROCDataSource).observe() 32 | } 33 | 34 | open override func createChatInputView() -> UIView { 35 | let inputView = ROCInputView() 36 | inputView.delegate = self 37 | return inputView 38 | } 39 | 40 | open override func createCollectionViewLayout() -> UICollectionViewLayout { 41 | let layout = ChatCollectionViewLayout() 42 | layout.delegate = self 43 | return layout 44 | } 45 | 46 | open override func createPresenterBuilders() -> [ChatItemType : [ChatItemPresenterBuilderProtocol]] { 47 | let textMessagePresenter = TextMessagePresenterBuilder( 48 | viewModelBuilder: ROCTextMessageViewModelBuilder(), 49 | interactionHandler: ROCTextMessageHandler(baseHandler: self.messageHandler) 50 | ) 51 | textMessagePresenter.textCellStyle = ROCTextMessageCollectionViewCellStyle() 52 | return [ 53 | "text/plain": [textMessagePresenter], 54 | ROCTimeSeparatorModel.chatItemType: [ROCTimeSeparatorPresenterBuilder()], 55 | ROCNameSeparatorModel.chatItemType: [ROCNameSeparatorPresenterBuilder()] 56 | ] 57 | } 58 | 59 | 60 | open func sendButtonDidTap(text: String) { 61 | } 62 | 63 | open func attachmentButtonDidTapped() { 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCBaseMessageHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCBaseMessageHandler.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Chatto 11 | import ChattoAdditions 12 | 13 | open class ROCBaseMessageHandler { 14 | 15 | open func userDidTapOnFailIcon(viewModel: ROCMessageViewModelProtocol) { 16 | 17 | } 18 | 19 | open func userDidTapOnAvatar(viewModel: ROCMessageViewModelProtocol) { 20 | } 21 | 22 | open func userDidTapOnBubble(viewModel: ROCMessageViewModelProtocol) { 23 | 24 | } 25 | 26 | open func userDidBeginLongPressOnBubble(viewModel: ROCMessageViewModelProtocol) { 27 | 28 | } 29 | 30 | open func userDidEndLongPressOnBubble(viewModel: ROCMessageViewModelProtocol) { 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCConfig.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/20/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | public struct ROCConfig { 12 | 13 | public struct Colors { 14 | public static var blueColor = UIColor(string: "#59569e") 15 | public static var lightGrayColor = UIColor(string: "#faf9f9") 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCDataSource.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Chatto 11 | import Realm 12 | import RealmSwift 13 | 14 | open class ROCDataSource: ChatDataSourceProtocol { 15 | 16 | open var chatItems: [ChatItemProtocol] = [] 17 | open var hasMorePrevious: Bool = false 18 | open var hasMoreNext: Bool = false 19 | open var delegate: ChatDataSourceDelegateProtocol? 20 | open func loadNext() { 21 | 22 | } 23 | open func loadPrevious() { 24 | 25 | } 26 | open func adjustNumberOfMessages(preferredMaxCount: Int?, focusPosition: Double, completion: ((Bool)) -> Void) { 27 | 28 | } 29 | 30 | 31 | private var _token: NotificationToken? 32 | public var results: Results { 33 | return _results 34 | } 35 | private var _results: Results 36 | 37 | public init(results: Results){ 38 | self._results = results 39 | } 40 | 41 | public func observe(){ 42 | self._token?.stop() 43 | self.delegate?.chatDataSourceDidUpdate(self, updateType: .reload) 44 | self._token = self._results.addNotificationBlock({ [weak self] (changes) in 45 | guard let `self` = self else { return } 46 | var chatItems = [ChatItemProtocol]() 47 | for r in self.results { 48 | let copy = T(value: r, schema: RLMSchema.partialShared()) 49 | let textMessageModel = ROCTextMessageModel(messageModel: copy, text: copy.text) 50 | chatItems.append(textMessageModel) 51 | } 52 | self.chatItems = chatItems 53 | self.delegate?.chatDataSourceDidUpdate(self) 54 | }) 55 | } 56 | 57 | public func reload(){ 58 | self.delegate?.chatDataSourceDidUpdate(self, updateType: .reload) 59 | } 60 | 61 | public func stop(){ 62 | _token?.stop() 63 | } 64 | 65 | deinit { 66 | _token?.stop() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCDecorator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCDecorator.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Chatto 11 | import ChattoAdditions 12 | 13 | final class ROCDecorator: ChatItemsDecoratorProtocol { 14 | struct Constants { 15 | static let shortSeparation: CGFloat = 3 16 | static let normalSeparation: CGFloat = 10 17 | static let timeIntervalThresholdToIncreaseSeparation: TimeInterval = 120 18 | } 19 | 20 | func decorateItems(_ chatItems: [ChatItemProtocol]) -> [DecoratedChatItem] { 21 | var decoratedChatItems = [DecoratedChatItem]() 22 | let calendar = Calendar.current 23 | 24 | for (index, chatItem) in chatItems.enumerated() { 25 | let next: ChatItemProtocol? = (index + 1 < chatItems.count) ? chatItems[index + 1] : nil 26 | let prev: ChatItemProtocol? = (index > 0) ? chatItems[index - 1] : nil 27 | 28 | let bottomMargin = self.separationAfterItem(chatItem, next: next) 29 | var showsTail = false 30 | let additionalItems = [DecoratedChatItem]() 31 | 32 | var addTimeSeparator = false 33 | var addNameSeparator = false 34 | 35 | var nameSeparator: DecoratedChatItem? = nil 36 | 37 | if let currentMessage = chatItem as? MessageModelProtocol { 38 | if let nextMessage = next as? MessageModelProtocol { 39 | showsTail = currentMessage.senderId != nextMessage.senderId 40 | } else { 41 | showsTail = true 42 | } 43 | 44 | addNameSeparator = showsTail 45 | 46 | if let previousMessage = prev as? MessageModelProtocol { 47 | addTimeSeparator = !calendar.isDate(currentMessage.date, inSameDayAs: previousMessage.date) 48 | addNameSeparator = currentMessage.senderId != previousMessage.senderId 49 | } else { 50 | addTimeSeparator = true 51 | addNameSeparator = false 52 | } 53 | 54 | 55 | if addTimeSeparator { 56 | let dateTimeStamp = DecoratedChatItem(chatItem: ROCTimeSeparatorModel(uid: "\(currentMessage.uid)-time-separator", date: currentMessage.date.toWeekDayAndDateString()), decorationAttributes: nil) 57 | decoratedChatItems.append(dateTimeStamp) 58 | } 59 | 60 | if addNameSeparator { 61 | let nameSeparatorModel = ROCNameSeparatorModel(uId: "\(currentMessage.uid)-name-seperator", name: currentMessage.senderId, isIncoming: currentMessage.isIncoming) 62 | nameSeparator = DecoratedChatItem(chatItem: nameSeparatorModel, decorationAttributes: nil) 63 | } 64 | } 65 | 66 | if let nameSeparator = nameSeparator { 67 | decoratedChatItems.append(nameSeparator) 68 | } 69 | 70 | decoratedChatItems.append(DecoratedChatItem( 71 | chatItem: chatItem, 72 | decorationAttributes: ChatItemDecorationAttributes(bottomMargin: bottomMargin, showsTail: showsTail, canShowAvatar: showsTail)) 73 | ) 74 | decoratedChatItems.append(contentsOf: additionalItems) 75 | 76 | 77 | } 78 | 79 | return decoratedChatItems 80 | } 81 | 82 | func separationAfterItem(_ current: ChatItemProtocol?, next: ChatItemProtocol?) -> CGFloat { 83 | guard let nexItem = next else { return 0 } 84 | guard let currentMessage = current as? MessageModelProtocol else { return Constants.normalSeparation } 85 | guard let nextMessage = nexItem as? MessageModelProtocol else { return Constants.normalSeparation } 86 | 87 | if currentMessage.senderId != nextMessage.senderId { 88 | return Constants.normalSeparation 89 | } else if nextMessage.date.timeIntervalSince(currentMessage.date) > Constants.timeIntervalThresholdToIncreaseSeparation { 90 | return Constants.normalSeparation 91 | } else { 92 | return Constants.shortSeparation 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCInputView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCInputView.swift 3 | // ROC 4 | // 5 | // Created by Max Alexander on 1/10/17. 6 | // Copyright © 2017 Max Alexander. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol ROCInputViewDelegate : class { 12 | func sendButtonDidTap(text: String) 13 | func attachmentButtonDidTapped() 14 | } 15 | 16 | public class ROCInputView : UIView, UITextViewDelegate { 17 | 18 | weak var delegate : ROCInputViewDelegate? 19 | 20 | let textView : UITextView = { 21 | let textView = UITextView() 22 | textView.textContainerInset = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 40) 23 | textView.isScrollEnabled = false 24 | textView.layer.borderColor = UIColor.lightGray.cgColor 25 | textView.layer.borderWidth = 1.0 26 | textView.layer.cornerRadius = 36 / 2 27 | textView.layer.masksToBounds = true 28 | textView.translatesAutoresizingMaskIntoConstraints = false 29 | textView.font = UIFont.systemFont(ofSize: 16) 30 | return textView 31 | }() 32 | 33 | lazy var topBorder : UIView = { 34 | let view = UIView() 35 | view.backgroundColor = UIColor.lightGray 36 | view.translatesAutoresizingMaskIntoConstraints = false 37 | return view 38 | }() 39 | 40 | lazy var attachmentButton : UIButton = { 41 | let button = UIButton() 42 | button.tintColor = UIColor.lightGray 43 | let image = ROCInternalImageGetter.loadImage(name: "attach_icon")?.withRenderingMode(.alwaysTemplate) 44 | button.setImage(image, for: .normal) 45 | button.translatesAutoresizingMaskIntoConstraints = false 46 | return button 47 | }() 48 | 49 | lazy var sendButton : UIButton = { 50 | let button = UIButton() 51 | button.layer.cornerRadius = 28 / 2 52 | button.layer.masksToBounds = true 53 | button.backgroundColor = ROCConfig.Colors.blueColor 54 | let image = ROCInternalImageGetter.loadImage(name: "send_icon")?.withRenderingMode(.alwaysTemplate) 55 | button.setImage(image, for: .normal) 56 | button.tintColor = .white 57 | button.imageView?.contentMode = .scaleAspectFit 58 | button.imageEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5) 59 | button.alpha = 0 60 | button.translatesAutoresizingMaskIntoConstraints = false 61 | return button 62 | }() 63 | 64 | init(){ 65 | super.init(frame: CGRect.zero) 66 | translatesAutoresizingMaskIntoConstraints = false 67 | backgroundColor = ROCConfig.Colors.lightGrayColor 68 | 69 | addSubview(textView) 70 | addSubview(attachmentButton) 71 | addSubview(topBorder) 72 | addSubview(sendButton) 73 | 74 | textView.delegate = self 75 | sendButton.addTarget(self, action: #selector(sendButtonDidTap), for: .touchUpInside) 76 | attachmentButton.addTarget(self, action: #selector(attachmentButtonDidTap), for: .touchUpInside) 77 | 78 | addConstraints( 79 | NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[topBorder]-0-|", options: [], metrics: nil, views: ["topBorder": topBorder]) 80 | ) 81 | addConstraints( 82 | NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[topBorder(1)]", options: [], metrics: nil, views: ["topBorder": topBorder]) 83 | ) 84 | 85 | addConstraints( 86 | NSLayoutConstraint.constraints(withVisualFormat: 87 | "H:|-8-[attachmentButton(33)]-8-[textView]-16-|", options: [], metrics: nil, views: 88 | ["attachmentButton": attachmentButton, "textView": textView]) 89 | ) 90 | addConstraints( 91 | NSLayoutConstraint.constraints(withVisualFormat: 92 | "V:[attachmentButton(33)]-8-|", options: [], metrics: nil, views: ["attachmentButton": attachmentButton]) 93 | ) 94 | addConstraints( 95 | NSLayoutConstraint.constraints(withVisualFormat: 96 | "V:|-8-[textView(>=37)]-8-|", options: [], metrics: nil, views: ["textView": textView]) 97 | ) 98 | addConstraints( 99 | NSLayoutConstraint.constraints(withVisualFormat: 100 | "H:[sendButton(28)]-22-|", options: [], metrics: nil, views: ["sendButton": sendButton]) 101 | ) 102 | addConstraints( 103 | NSLayoutConstraint.constraints(withVisualFormat: 104 | "V:[sendButton(28)]-13-|", options: [], metrics: nil, views: ["sendButton": sendButton]) 105 | ) 106 | 107 | } 108 | 109 | public func textViewDidChange(_ textView: UITextView) { 110 | evaluateSendButtonAlpha() 111 | } 112 | 113 | func evaluateSendButtonAlpha(){ 114 | let text = textView.text ?? "" 115 | let alpha : CGFloat = text.characters.count == 0 ? 0 : 1 116 | UIView.animate(withDuration: 0.25) { 117 | self.sendButton.alpha = alpha 118 | } 119 | } 120 | 121 | func sendButtonDidTap(){ 122 | delegate?.sendButtonDidTap(text: textView.text) 123 | textView.text = "" 124 | evaluateSendButtonAlpha() 125 | } 126 | 127 | func attachmentButtonDidTap(){ 128 | delegate?.attachmentButtonDidTapped() 129 | } 130 | 131 | override public func layoutSubviews() { 132 | self.updateConstraints() // Interface rotation or size class changes will reset constraints as defined in interface builder -> constraintsForVisibleTextView will be activated 133 | super.layoutSubviews() 134 | } 135 | 136 | required public init?(coder aDecoder: NSCoder) { 137 | fatalError("init(coder:) has not been implemented") 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCInternalImageGetter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCInternalImageGetter.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/21/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class ROCInternalImageGetter { 12 | 13 | class func loadImage(name: String) -> UIImage? { 14 | let podBundle = Bundle(for: ROCInternalImageGetter.self) 15 | return UIImage(named: name, in: podBundle, compatibleWith: nil) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCMessageModelProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCMessageModelProtocol.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import ChattoAdditions 11 | 12 | 13 | public protocol ROCMessageModelProtocol: MessageModelProtocol { 14 | var userDisplayName: String { get set } 15 | } 16 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCMessageViewModelProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCMessageViewModelProtocol.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol ROCMessageViewModelProtocol { 12 | var messageModel: ROCMessageModelProtocol { get } 13 | } 14 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCNameSeparatorCellLayoutModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCNameSeparatorCellLayoutModel.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/28/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | import ChattoAdditions 12 | 13 | open class ROCNameSeparatorCellLayoutModel { 14 | let layoutContext: LayoutContext 15 | var textFrame: CGRect = CGRect.zero 16 | var size: CGSize = CGSize.zero 17 | 18 | init(layoutContext: LayoutContext){ 19 | self.layoutContext = layoutContext 20 | } 21 | 22 | struct LayoutContext: Equatable, Hashable { 23 | let text: String 24 | let font: UIFont 25 | let textInsets: UIEdgeInsets 26 | let isIncoming: Bool 27 | let preferredMaxLayoutWidth: CGFloat 28 | 29 | var hashValue: Int { 30 | return combineAndMakeHash(hashes: [self.text.hashValue, self.textInsets.bma_hashValue, self.preferredMaxLayoutWidth.hashValue, self.font.hashValue]) 31 | } 32 | 33 | static func == (lhs: ROCNameSeparatorCellLayoutModel.LayoutContext, rhs: ROCNameSeparatorCellLayoutModel.LayoutContext) -> Bool { 34 | let lhsValues = (lhs.text, lhs.textInsets, lhs.font, lhs.preferredMaxLayoutWidth) 35 | let rhsValues = (rhs.text, rhs.textInsets, rhs.font, rhs.preferredMaxLayoutWidth) 36 | return lhsValues == rhsValues 37 | } 38 | } 39 | 40 | 41 | func calculateLayout() { 42 | let textHorizontalInset = self.layoutContext.textInsets.bma_horziontalInset 43 | let maxTextWidth = self.layoutContext.preferredMaxLayoutWidth - textHorizontalInset 44 | let textSize = self.textSizeThatFitsWidth(maxTextWidth) 45 | let bubbleSize = textSize.bma_outsetBy(dx: textHorizontalInset, dy: self.layoutContext.textInsets.bma_verticalInset) 46 | self.textFrame = CGRect(origin: CGPoint.zero, size: bubbleSize) 47 | self.size = bubbleSize 48 | } 49 | 50 | private func textSizeThatFitsWidth(_ width: CGFloat) -> CGSize { 51 | let textContainer: NSTextContainer = { 52 | let size = CGSize(width: width, height: .greatestFiniteMagnitude) 53 | let container = NSTextContainer(size: size) 54 | container.lineFragmentPadding = 0 55 | return container 56 | }() 57 | 58 | let textStorage = self.replicateUITextViewNSTextStorage() 59 | let layoutManager: NSLayoutManager = { 60 | let layoutManager = NSLayoutManager() 61 | layoutManager.addTextContainer(textContainer) 62 | textStorage.addLayoutManager(layoutManager) 63 | return layoutManager 64 | }() 65 | 66 | let rect = layoutManager.usedRect(for: textContainer) 67 | return rect.size.bma_round() 68 | } 69 | 70 | private func replicateUITextViewNSTextStorage() -> NSTextStorage { 71 | // See https://github.com/badoo/Chatto/issues/129 72 | return NSTextStorage(string: self.layoutContext.text, attributes: [ 73 | NSFontAttributeName: self.layoutContext.font, 74 | "NSOriginalFont": self.layoutContext.font 75 | ]) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCNameSeparatorCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCNameSeparatorCollectionViewCell.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | 12 | open class ROCNameSeparatorCollectionViewCell: UICollectionViewCell { 13 | 14 | private let label: UILabel = UILabel() 15 | 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | self.commonInit() 19 | } 20 | 21 | required public init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | self.commonInit() 24 | } 25 | 26 | private func commonInit() { 27 | self.label.font = UIFont.systemFont(ofSize: 12) 28 | self.label.textAlignment = .center 29 | self.label.textColor = UIColor.gray 30 | self.contentView.addSubview(label) 31 | } 32 | 33 | 34 | open func setTextOnLabel(_ text: String, _ alignment: NSTextAlignment) { 35 | self.label.text = text 36 | self.label.textAlignment = alignment 37 | self.setNeedsLayout() 38 | } 39 | 40 | override open func layoutSubviews() { 41 | super.layoutSubviews() 42 | self.label.frame = self.contentView.frame 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCNameSeparatorPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCNameSeparatorPresenter.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | 12 | open class ROCNameSeparatorPresenter: ChatItemPresenterProtocol { 13 | 14 | let model: ROCNameSeparatorModel 15 | init (model: ROCNameSeparatorModel) { 16 | self.model = model 17 | } 18 | 19 | private static let cellReuseIdentifier = ROCNameSeparatorCollectionViewCell.self.description() 20 | 21 | public static func registerCells(_ collectionView: UICollectionView) { 22 | collectionView.register(ROCNameSeparatorCollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier) 23 | } 24 | 25 | public func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell { 26 | return collectionView.dequeueReusableCell(withReuseIdentifier: ROCNameSeparatorPresenter.cellReuseIdentifier, for: indexPath) 27 | } 28 | 29 | public func configureCell(_ cell: UICollectionViewCell, decorationAttributes: ChatItemDecorationAttributesProtocol?) { 30 | guard let cell = cell as? ROCNameSeparatorCollectionViewCell else { 31 | assert(false, "expecting status cell") 32 | return 33 | } 34 | cell.setTextOnLabel(model.name, model.isIncoming ? .left : .right) 35 | 36 | } 37 | 38 | open var canCalculateHeightInBackground: Bool { 39 | return true 40 | } 41 | 42 | open func heightForCell(maximumWidth width: CGFloat, decorationAttributes: ChatItemDecorationAttributesProtocol?) -> CGFloat { 43 | return 24 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCNameSeparatorPresenterBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCNameSeparatorPresenterBuilder.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | 10 | import UIKit 11 | import Chatto 12 | 13 | open class ROCNameSeparatorPresenterBuilder: ChatItemPresenterBuilderProtocol { 14 | 15 | public init(){ 16 | 17 | } 18 | 19 | public func canHandleChatItem(_ chatItem: ChatItemProtocol) -> Bool { 20 | return chatItem is ROCNameSeparatorModel 21 | } 22 | 23 | public func createPresenterWithChatItem(_ chatItem: ChatItemProtocol) -> ChatItemPresenterProtocol { 24 | assert(self.canHandleChatItem(chatItem)) 25 | return ROCNameSeparatorPresenter(model: chatItem as! ROCNameSeparatorModel) 26 | } 27 | 28 | public var presenterType: ChatItemPresenterProtocol.Type { 29 | return ROCNameSeparatorPresenter.self 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCNameSeperatorModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCNameSeperatorModel.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | 12 | open class ROCNameSeparatorModel: ChatItemProtocol { 13 | 14 | public let uid: String 15 | public let type: String = ROCNameSeparatorModel.chatItemType 16 | public let name: String 17 | public let isIncoming: Bool 18 | 19 | public static var chatItemType: ChatItemType { 20 | return "ROCNameSeparatorModel" 21 | } 22 | 23 | public init(uId: String, name: String, isIncoming: Bool) { 24 | self.uid = uId 25 | self.name = name 26 | self.isIncoming = isIncoming 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTextMessageCollectionViewCellStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTextMessageCollectionViewCellStyle.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | 10 | import UIKit 11 | import Chatto 12 | import ChattoAdditions 13 | 14 | class ROCTextMessageCollectionViewCellStyle : TextMessageCollectionViewCellDefaultStyle { 15 | 16 | override func textFont(viewModel: TextMessageViewModelProtocol, isSelected: Bool) -> UIFont { 17 | return UIFont.systemFont(ofSize: 16) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTextMessageHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTextMessageHandler.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import ChattoAdditions 11 | 12 | 13 | class ROCTextMessageHandler: BaseMessageInteractionHandlerProtocol { 14 | private let baseHandler: ROCBaseMessageHandler 15 | init (baseHandler: ROCBaseMessageHandler) { 16 | self.baseHandler = baseHandler 17 | } 18 | 19 | func userDidTapOnFailIcon(viewModel: ROCTextMessageViewModel, failIconView: UIView) { 20 | self.baseHandler.userDidTapOnFailIcon(viewModel: viewModel) 21 | } 22 | 23 | func userDidTapOnAvatar(viewModel: ROCTextMessageViewModel) { 24 | self.baseHandler.userDidTapOnAvatar(viewModel: viewModel) 25 | } 26 | 27 | func userDidTapOnBubble(viewModel: ROCTextMessageViewModel) { 28 | self.baseHandler.userDidTapOnBubble(viewModel: viewModel) 29 | } 30 | 31 | func userDidBeginLongPressOnBubble(viewModel: ROCTextMessageViewModel) { 32 | self.baseHandler.userDidBeginLongPressOnBubble(viewModel: viewModel) 33 | } 34 | 35 | func userDidEndLongPressOnBubble(viewModel: ROCTextMessageViewModel) { 36 | self.baseHandler.userDidEndLongPressOnBubble(viewModel: viewModel) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTextMessageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTexMessageModel.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import ChattoAdditions 11 | 12 | open class ROCTextMessageModel: TextMessageModel { 13 | 14 | public override init(messageModel: ROCChatMessage, text: String) { 15 | super.init(messageModel: messageModel, text: text) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTextMessageViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTextMessageViewModel.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import ChattoAdditions 11 | 12 | public class ROCTextMessageViewModel: TextMessageViewModel, ROCMessageViewModelProtocol { 13 | 14 | override init(textMessage: ROCTextMessageModel, messageViewModel: MessageViewModelProtocol) { 15 | super.init(textMessage: textMessage, messageViewModel: messageViewModel) 16 | } 17 | 18 | public var messageModel: ROCMessageModelProtocol { 19 | return self.textMessage as! ROCMessageModelProtocol 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTextMessageViewModelBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTextMessageViewModelBuilder.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/17/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import ChattoAdditions 11 | 12 | class ROCTextMessageViewModelBuilder: ViewModelBuilderProtocol { 13 | init() {} 14 | 15 | let messageViewModelBuilder = MessageViewModelDefaultBuilder() 16 | 17 | func createViewModel(_ textMessage: ROCTextMessageModel) -> ROCTextMessageViewModel { 18 | let messageViewModel = self.messageViewModelBuilder.createMessageViewModel(textMessage) 19 | let textMessageViewModel = ROCTextMessageViewModel(textMessage: textMessage, messageViewModel: messageViewModel) 20 | return textMessageViewModel 21 | } 22 | 23 | func canCreateViewModel(fromModel model: Any) -> Bool { 24 | return model is ROCTextMessageModel 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTimeSeparatorCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTimeSeparatorCollectionViewCell.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Chatto 12 | 13 | open class ROCTimeSeparatorCollectionViewCell: UICollectionViewCell { 14 | 15 | private let label: UILabel = UILabel() 16 | 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | self.commonInit() 20 | } 21 | 22 | required public init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | self.commonInit() 25 | } 26 | 27 | private func commonInit() { 28 | self.label.font = UIFont.systemFont(ofSize: 12) 29 | self.label.textAlignment = .center 30 | self.label.textColor = UIColor.gray 31 | self.contentView.addSubview(label) 32 | } 33 | 34 | var text: String = "" { 35 | didSet { 36 | if oldValue != text { 37 | self.setTextOnLabel(text) 38 | } 39 | } 40 | } 41 | 42 | private func setTextOnLabel(_ text: String) { 43 | self.label.text = text 44 | self.setNeedsLayout() 45 | } 46 | 47 | override open func layoutSubviews() { 48 | super.layoutSubviews() 49 | self.label.bounds.size = self.label.sizeThatFits(self.contentView.bounds.size) 50 | self.label.center = self.contentView.center 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTimeSeparatorModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTimeSeperatorModel.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | 12 | open class ROCTimeSeparatorModel: ChatItemProtocol { 13 | 14 | public let uid: String 15 | public let type: String = ROCTimeSeparatorModel.chatItemType 16 | public let date: String 17 | 18 | public static var chatItemType: ChatItemType { 19 | return "ROCTimeSeparatorModel" 20 | } 21 | 22 | public init(uid: String, date: String) { 23 | self.date = date 24 | self.uid = uid 25 | } 26 | } 27 | 28 | extension Date { 29 | // Have a time stamp formatter to avoid keep creating new ones. This improves performance 30 | private static let weekdayAndDateStampDateFormatter: DateFormatter = { 31 | let dateFormatter = DateFormatter() 32 | dateFormatter.timeZone = TimeZone.autoupdatingCurrent 33 | dateFormatter.dateFormat = "EEEE, MMM dd yyyy" // "Monday, Mar 7 2016" 34 | return dateFormatter 35 | }() 36 | 37 | public func toWeekDayAndDateString() -> String { 38 | return Date.weekdayAndDateStampDateFormatter.string(from: self) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTimeSeparatorPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCROCTimeSeparatorPresenter.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Chatto 12 | 13 | open class ROCTimeSeparatorPresenter: ChatItemPresenterProtocol { 14 | 15 | let model: ROCTimeSeparatorModel 16 | init (model: ROCTimeSeparatorModel) { 17 | self.model = model 18 | } 19 | 20 | private static let cellReuseIdentifier = ROCTimeSeparatorCollectionViewCell.self.description() 21 | 22 | public static func registerCells(_ collectionView: UICollectionView) { 23 | collectionView.register(ROCTimeSeparatorCollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier) 24 | } 25 | 26 | public func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell { 27 | return collectionView.dequeueReusableCell(withReuseIdentifier: ROCTimeSeparatorPresenter.cellReuseIdentifier, for: indexPath) 28 | } 29 | 30 | public func configureCell(_ cell: UICollectionViewCell, decorationAttributes: ChatItemDecorationAttributesProtocol?) { 31 | guard let ROCTimeSeparatorCell = cell as? ROCTimeSeparatorCollectionViewCell else { 32 | assert(false, "expecting status cell") 33 | return 34 | } 35 | 36 | ROCTimeSeparatorCell.text = self.model.date 37 | } 38 | 39 | open var canCalculateHeightInBackground: Bool { 40 | return true 41 | } 42 | 43 | open func heightForCell(maximumWidth width: CGFloat, decorationAttributes: ChatItemDecorationAttributesProtocol?) -> CGFloat { 44 | return 24 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ROCController/Classes/ROCTimeSeparatorPresenterBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ROCTimeseparatorPresenterBuilder.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/27/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | import Chatto 11 | 12 | open class ROCTimeSeparatorPresenterBuilder: ChatItemPresenterBuilderProtocol { 13 | 14 | public init(){ 15 | 16 | } 17 | 18 | public func canHandleChatItem(_ chatItem: ChatItemProtocol) -> Bool { 19 | return chatItem is ROCTimeSeparatorModel 20 | } 21 | 22 | public func createPresenterWithChatItem(_ chatItem: ChatItemProtocol) -> ChatItemPresenterProtocol { 23 | assert(self.canHandleChatItem(chatItem)) 24 | return ROCTimeSeparatorPresenter(model: chatItem as! ROCTimeSeparatorModel) 25 | } 26 | 27 | public var presenterType: ChatItemPresenterProtocol.Type { 28 | return ROCTimeSeparatorPresenter.self 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /ROCController/Classes/UIColor+Hex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Hex.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/23/17. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | internal convenience init(string: String) { 13 | var chars = Array(string.hasPrefix("#") ? string.characters.dropFirst() : string.characters) 14 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 1 15 | switch chars.count { 16 | case 3: 17 | chars = [chars[0], chars[0], chars[1], chars[1], chars[2], chars[2]] 18 | fallthrough 19 | case 6: 20 | chars = ["F","F"] + chars 21 | fallthrough 22 | case 8: 23 | alpha = CGFloat(strtoul(String(chars[0...1]), nil, 16)) / 255 24 | red = CGFloat(strtoul(String(chars[2...3]), nil, 16)) / 255 25 | green = CGFloat(strtoul(String(chars[4...5]), nil, 16)) / 255 26 | blue = CGFloat(strtoul(String(chars[6...7]), nil, 16)) / 255 27 | default: 28 | alpha = 0 29 | } 30 | self.init(red: red, green: green, blue: blue, alpha: alpha) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ROCController/Classes/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // Pods 4 | // 5 | // Created by Maximilian Alexander on 3/28/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | @inline(__always) 12 | public func combineAndMakeHash(hashes: [Int]) -> Int { 13 | return hashes.reduce(0, { 31 &* $0 &+ $1.hashValue }) 14 | } 15 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------