├── .gitignore ├── LICENSE ├── Podfile ├── README.md ├── ScreenShot └── 1.png ├── TLMessageView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── TLMessageView.xcworkspace └── contents.xcworkspacedata └── TLMessageView ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── actionbar_audio_call_icon.imageset │ ├── Contents.json │ └── actionbar_audio_call_icon.png ├── actionbar_camera_icon.imageset │ ├── Contents.json │ └── actionbar_camera_icon.png ├── actionbar_file_icon.imageset │ ├── Contents.json │ └── actionbar_file_icon.png ├── actionbar_location_icon.imageset │ ├── Contents.json │ └── actionbar_location_icon.png ├── actionbar_picture_icon.imageset │ ├── Contents.json │ └── actionbar_picture_icon.png ├── actionbar_video_call_icon.imageset │ ├── Contents.json │ └── actionbar_video_call_icon.png ├── back.imageset │ ├── Contents.json │ └── back1.png ├── chat_from_bg_normal.imageset │ ├── Contents.json │ └── chat_from_bg_normal@2x.png ├── chat_from_imagebg_normal.imageset │ ├── Contents.json │ └── chat_from_imagebg_normal@2x.png ├── chat_to_bg_normal.imageset │ ├── Contents.json │ └── chat_to_bg_normal@2x.png ├── chat_to_imagebg_normal.imageset │ ├── Contents.json │ └── chat_to_imagebg_normal@2x.png ├── chatfrom_doctor_icon.imageset │ ├── Contents.json │ └── chatfrom_doctor_icon.png ├── emoji_btn_delete.imageset │ ├── Contents.json │ └── emoji_btn_delete@2x.png ├── emoji_btn_normal.imageset │ ├── Contents.json │ └── emoji_btn_normal.png ├── emoji_hover.imageset │ ├── Contents.json │ └── emoji_hover.png ├── from_voice.imageset │ ├── Contents.json │ └── from_voice.png ├── from_voice_1.imageset │ ├── Contents.json │ └── from_voice_1.png ├── from_voice_2.imageset │ ├── Contents.json │ └── from_voice_2.png ├── from_voice_3.imageset │ ├── Contents.json │ └── from_voice_3.png ├── icon_+.imageset │ ├── Contents.json │ ├── icon_+@2x.png │ └── icon_+@3x.png ├── icon_duijiang.imageset │ ├── Contents.json │ ├── icon_duijiang@2x.png │ └── icon_duijiang@3x.png ├── icon_kyb.imageset │ ├── Contents.json │ ├── icon_kyb@2x.png │ └── icon_kyb@3x.png ├── icon_xiaolian.imageset │ ├── Contents.json │ ├── icon_xiaolian@2x.png │ └── icon_xiaolian@3x.png ├── news_failinsend.imageset │ ├── Contents.json │ ├── news_failinsend@2x.png │ └── news_failinsend@3x.png ├── photo_preview_selected.imageset │ ├── Contents.json │ └── photo_preview_selected.png ├── photo_preview_unselected.imageset │ ├── Contents.json │ └── photo_preview_unselected.png ├── photopicker_state_normal.imageset │ ├── Contents.json │ └── photopicker_state_normal.png ├── photopicker_state_selected.imageset │ ├── Contents.json │ └── photopicker_state_selected.png ├── red_background.imageset │ ├── Contents.json │ └── red_background@2x.png ├── return.imageset │ ├── Contents.json │ └── return.png ├── selected_full.imageset │ ├── Contents.json │ └── selected_full.png ├── to_voice.imageset │ ├── Contents.json │ └── to_voice.png ├── to_voice_1.imageset │ ├── Contents.json │ └── to_voice_1.png ├── to_voice_2.imageset │ ├── Contents.json │ └── to_voice_2.png ├── to_voice_3.imageset │ ├── Contents.json │ └── to_voice_3.png ├── unselected_full.imageset │ ├── Contents.json │ └── unselected_full.png ├── voice_1.imageset │ ├── Contents.json │ └── voice_1.png ├── voice_2.imageset │ ├── Contents.json │ └── voice_2.png ├── voice_3.imageset │ ├── Contents.json │ └── voice_3.png ├── voice_4.imageset │ ├── Contents.json │ └── voice_4.png ├── voice_5.imageset │ ├── Contents.json │ └── voice_5.png ├── voice_6.imageset │ ├── Contents.json │ └── voice_6.png ├── voice_7.imageset │ ├── Contents.json │ └── voice_7.png └── voice_8.imageset │ ├── Contents.json │ └── voice_8.png ├── Base.lproj └── LaunchScreen.storyboard ├── Chat ├── Cell │ ├── TLLocationMessageCell.h │ ├── TLLocationMessageCell.m │ ├── TLMessageCell.h │ ├── TLMessageCell.m │ ├── TLPhotoMessageCell.h │ ├── TLPhotoMessageCell.m │ ├── TLPhotoThumbCell.h │ ├── TLPhotoThumbCell.m │ ├── TLTextMessageCell.h │ ├── TLTextMessageCell.m │ ├── TLVoiceMessageCell.h │ └── TLVoiceMessageCell.m ├── Controller │ ├── TLChatViewController.h │ ├── TLChatViewController.m │ ├── TLLocationViewController.h │ ├── TLLocationViewController.m │ ├── TLPhotoPickerViewController.h │ ├── TLPhotoPickerViewController.m │ ├── TLPhotoPreviewViewController.h │ └── TLPhotoPreviewViewController.m ├── Networking │ ├── APIDebug.h │ └── APIDebug.m ├── Vendor │ ├── LPlaceholderTextView.h │ ├── LPlaceholderTextView.m │ ├── NSDate+Utils.h │ ├── NSDate+Utils.m │ ├── PHAsset+Extend.h │ ├── PHAsset+Extend.m │ ├── TLRCManager.h │ ├── TLRCManager.m │ ├── TLRecordVoice.h │ ├── TLRecordVoice.m │ ├── TLVoicePlayer.h │ └── TLVoicePlayer.m └── View │ ├── TLButton.h │ ├── TLButton.m │ ├── TLChatEmojiBoard.h │ ├── TLChatEmojiBoard.m │ ├── TLChatInputView.h │ ├── TLChatInputView.m │ ├── TLPhotoBrowser.h │ ├── TLPhotoBrowser.m │ ├── TLPluginBoardView.h │ ├── TLPluginBoardView.m │ ├── TLRecordVoiceHUD.h │ └── TLRecordVoiceHUD.m ├── Info.plist ├── TLProjectMacro.h ├── account.plist └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | Pods/ 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | .DS_Store 21 | *.DS_Store 22 | *.xcuserdata 23 | *.dSYMs.zip 24 | *.xcarchive 25 | BiLin/BLVersionHeader.h 26 | *.xcscmblueprint 27 | .flowconfig 28 | tags 29 | npm-debug.log 30 | Podfile.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 GarryGuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | #use_frameworks! 2 | 3 | platform :ios, '8.0' 4 | 5 | #inhibit_all_warnings! 6 | 7 | target "TLMessageView" do 8 | pod 'Masonry' 9 | pod 'RongCloudIMLib' 10 | pod 'UITableView+FDTemplateLayoutCell', '~> 1.6' 11 | pod 'SDWebImage' 12 | end 13 | 14 | post_install do |installer| 15 | installer.pods_project.targets.each do |target| 16 | target.build_configurations.each do |config| 17 | config.build_settings['ENABLE_BITCODE'] = 'NO' 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TLMessage 2 | ================== 3 | 4 | # TODO 5 | * 群聊 6 | * 文件消息 7 | 8 | # Introduce 9 | * 这是一个IMUI库。对接了目前比较流行的RongCloud实现聊天功能。 10 | 11 | # System Requirement 12 | * iOS 8.0 or later 13 | * Xcode 8.0 or later 14 | 15 | # Character 16 | * 接入融云IMLib,支持Text/Image/Voice/Location消息收发 17 | * 支持发送状态提示,失败重发 18 | * 完全使用AutoLayout布局 19 | * 支持扩展面板扩展 20 | * 支持Emoji输入 21 | 22 | # Depend on Framwork 23 | * Masonry AutoLayout 24 | * FDTemplateLayoutCell Cell自适应高度 25 | * SDWebImage 26 | * RongCloudIMLib 融云通讯能力库 27 | 28 | # Usage 29 | * 克隆项目后,需要使用Cocoapods install 30 | 31 | # Warning! 32 | * 部分图片素材来源于融云IMKit,使用前请替换资源图片 33 | * 目前项目尚处于开发阶段,不建议直接在企业项目中使用。 34 | 35 | # Images 36 | 37 | -------------------------------------------------------------------------------- /ScreenShot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/ScreenShot/1.png -------------------------------------------------------------------------------- /TLMessageView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TLMessageView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TLMessageView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TLMessageView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TLChatViewController.h" 11 | #import "TLRCManager.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 22 | 23 | TLChatViewController *vc = [TLChatViewController new]; 24 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; 25 | 26 | self.window.rootViewController = nav; 27 | [self.window makeKeyAndVisible]; 28 | 29 | [[TLRCManager shareManager] initEnv]; 30 | 31 | return YES; 32 | } 33 | 34 | - (void)applicationWillResignActive:(UIApplication *)application { 35 | // 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. 36 | // 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. 37 | } 38 | 39 | - (void)applicationDidEnterBackground:(UIApplication *)application { 40 | // 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. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | } 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application { 45 | // 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. 46 | } 47 | 48 | - (void)applicationDidBecomeActive:(UIApplication *)application { 49 | // 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. 50 | } 51 | 52 | - (void)applicationWillTerminate:(UIApplication *)application { 53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_audio_call_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "actionbar_audio_call_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_audio_call_icon.imageset/actionbar_audio_call_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/actionbar_audio_call_icon.imageset/actionbar_audio_call_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_camera_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "actionbar_camera_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_camera_icon.imageset/actionbar_camera_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/actionbar_camera_icon.imageset/actionbar_camera_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_file_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "actionbar_file_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_file_icon.imageset/actionbar_file_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/actionbar_file_icon.imageset/actionbar_file_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_location_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "actionbar_location_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_location_icon.imageset/actionbar_location_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/actionbar_location_icon.imageset/actionbar_location_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_picture_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "actionbar_picture_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_picture_icon.imageset/actionbar_picture_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/actionbar_picture_icon.imageset/actionbar_picture_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_video_call_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "actionbar_video_call_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/actionbar_video_call_icon.imageset/actionbar_video_call_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/actionbar_video_call_icon.imageset/actionbar_video_call_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "back1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/back.imageset/back1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/back.imageset/back1.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_from_bg_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "chat_from_bg_normal@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_from_bg_normal.imageset/chat_from_bg_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/chat_from_bg_normal.imageset/chat_from_bg_normal@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_from_imagebg_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "chat_from_imagebg_normal@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_from_imagebg_normal.imageset/chat_from_imagebg_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/chat_from_imagebg_normal.imageset/chat_from_imagebg_normal@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_to_bg_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "chat_to_bg_normal@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_to_bg_normal.imageset/chat_to_bg_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/chat_to_bg_normal.imageset/chat_to_bg_normal@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_to_imagebg_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "chat_to_imagebg_normal@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chat_to_imagebg_normal.imageset/chat_to_imagebg_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/chat_to_imagebg_normal.imageset/chat_to_imagebg_normal@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chatfrom_doctor_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "chatfrom_doctor_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/chatfrom_doctor_icon.imageset/chatfrom_doctor_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/chatfrom_doctor_icon.imageset/chatfrom_doctor_icon.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/emoji_btn_delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "emoji_btn_delete@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/emoji_btn_delete.imageset/emoji_btn_delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/emoji_btn_delete.imageset/emoji_btn_delete@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/emoji_btn_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "emoji_btn_normal.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/emoji_btn_normal.imageset/emoji_btn_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/emoji_btn_normal.imageset/emoji_btn_normal.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/emoji_hover.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "emoji_hover.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/emoji_hover.imageset/emoji_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/emoji_hover.imageset/emoji_hover.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "from_voice.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice.imageset/from_voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/from_voice.imageset/from_voice.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "from_voice_1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice_1.imageset/from_voice_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/from_voice_1.imageset/from_voice_1.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "from_voice_2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice_2.imageset/from_voice_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/from_voice_2.imageset/from_voice_2.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "from_voice_3.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/from_voice_3.imageset/from_voice_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/from_voice_3.imageset/from_voice_3.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_+.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_+@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon_+@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_+.imageset/icon_+@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_+.imageset/icon_+@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_+.imageset/icon_+@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_+.imageset/icon_+@3x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_duijiang.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_duijiang@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon_duijiang@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_duijiang.imageset/icon_duijiang@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_duijiang.imageset/icon_duijiang@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_duijiang.imageset/icon_duijiang@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_duijiang.imageset/icon_duijiang@3x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_kyb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_kyb@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon_kyb@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_kyb.imageset/icon_kyb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_kyb.imageset/icon_kyb@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_kyb.imageset/icon_kyb@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_kyb.imageset/icon_kyb@3x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_xiaolian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_xiaolian@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "icon_xiaolian@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_xiaolian.imageset/icon_xiaolian@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_xiaolian.imageset/icon_xiaolian@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/icon_xiaolian.imageset/icon_xiaolian@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/icon_xiaolian.imageset/icon_xiaolian@3x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/news_failinsend.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "news_failinsend@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "news_failinsend@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/news_failinsend.imageset/news_failinsend@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/news_failinsend.imageset/news_failinsend@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/news_failinsend.imageset/news_failinsend@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/news_failinsend.imageset/news_failinsend@3x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photo_preview_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "photo_preview_selected.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photo_preview_selected.imageset/photo_preview_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/photo_preview_selected.imageset/photo_preview_selected.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photo_preview_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "photo_preview_unselected.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photo_preview_unselected.imageset/photo_preview_unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/photo_preview_unselected.imageset/photo_preview_unselected.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photopicker_state_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "photopicker_state_normal.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photopicker_state_normal.imageset/photopicker_state_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/photopicker_state_normal.imageset/photopicker_state_normal.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photopicker_state_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "photopicker_state_selected.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/photopicker_state_selected.imageset/photopicker_state_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/photopicker_state_selected.imageset/photopicker_state_selected.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/red_background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "red_background@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/red_background.imageset/red_background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/red_background.imageset/red_background@2x.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/return.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "return.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/return.imageset/return.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/return.imageset/return.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/selected_full.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "selected_full.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/selected_full.imageset/selected_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/selected_full.imageset/selected_full.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "to_voice.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice.imageset/to_voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/to_voice.imageset/to_voice.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "to_voice_1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice_1.imageset/to_voice_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/to_voice_1.imageset/to_voice_1.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "to_voice_2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice_2.imageset/to_voice_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/to_voice_2.imageset/to_voice_2.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "to_voice_3.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/to_voice_3.imageset/to_voice_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/to_voice_3.imageset/to_voice_3.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/unselected_full.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "unselected_full.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/unselected_full.imageset/unselected_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/unselected_full.imageset/unselected_full.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_1.imageset/voice_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_1.imageset/voice_1.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_2.imageset/voice_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_2.imageset/voice_2.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_3.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_3.imageset/voice_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_3.imageset/voice_3.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_4.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_4.imageset/voice_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_4.imageset/voice_4.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_5.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_5.imageset/voice_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_5.imageset/voice_5.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_6.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_6.imageset/voice_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_6.imageset/voice_6.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_7.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_7.imageset/voice_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_7.imageset/voice_7.png -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "voice_8.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TLMessageView/Assets.xcassets/voice_8.imageset/voice_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timelessg/TLMessage/5d4a530620534adcede7276192d1e79b37d23ead/TLMessageView/Assets.xcassets/voice_8.imageset/voice_8.png -------------------------------------------------------------------------------- /TLMessageView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLLocationMessageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLLocationMessageCell.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/23. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLMessageCell.h" 10 | 11 | @interface TLLocationMessageCell : TLMessageCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLLocationMessageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLLocationMessageCell.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/23. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLLocationMessageCell.h" 10 | #import 11 | 12 | @interface TLLocationMessageCell () 13 | @property(nonatomic,strong)UIImageView *previewImageView; 14 | @property(nonatomic,strong)UILabel *placeLabel; 15 | @end 16 | 17 | @implementation TLLocationMessageCell 18 | -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 19 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 20 | [self.bubbleImageView addSubview:self.previewImageView]; 21 | [self.previewImageView mas_makeConstraints:^(MASConstraintMaker *make) { 22 | make.edges.equalTo(self.bubbleImageView).priorityHigh(); 23 | }]; 24 | 25 | [self.previewImageView addSubview:self.placeLabel]; 26 | [self.placeLabel mas_makeConstraints:^(MASConstraintMaker *make) { 27 | make.left.equalTo(self.previewImageView.mas_left).offset(0); 28 | make.right.equalTo(self.previewImageView.mas_right).offset(0); 29 | make.bottom.equalTo(self.previewImageView.mas_bottom).offset(0); 30 | make.height.mas_offset(30); 31 | }]; 32 | 33 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didClickLocation)]; 34 | [self.previewImageView addGestureRecognizer:tap]; 35 | } 36 | return self; 37 | } 38 | -(void)didClickLocation{ 39 | RCLocationMessage *locMessage = (RCLocationMessage *)self.message.content; 40 | 41 | MKMapItem *currentLocation = [MKMapItem mapItemForCurrentLocation]; 42 | MKMapItem *toLocation = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithCoordinate:locMessage.location addressDictionary:nil]]; 43 | toLocation.name = locMessage.locationName; 44 | NSDictionary *launchOptions = [NSDictionary 45 | dictionaryWithObjects:[NSArray arrayWithObjects:MKLaunchOptionsDirectionsModeDriving, [NSNumber numberWithBool:YES], nil] 46 | forKeys:[NSArray arrayWithObjects:MKLaunchOptionsDirectionsModeKey, MKLaunchOptionsShowsTrafficKey, nil]]; 47 | [MKMapItem openMapsWithItems:[NSArray arrayWithObjects:currentLocation, toLocation, nil] 48 | launchOptions:launchOptions]; 49 | } 50 | -(void)updateMessage:(RCMessage *)message showDate:(BOOL)showDate{ 51 | [super updateMessage:message showDate:showDate]; 52 | RCLocationMessage *locMessage = (RCLocationMessage *)message.content; 53 | self.previewImageView.image = locMessage.thumbnailImage; 54 | self.placeLabel.text = locMessage.locationName; 55 | 56 | CGSize size = CGSizeMake(187.5, 150); 57 | 58 | UIImageView *imageViewMask = [[UIImageView alloc] initWithImage:self.bubbleImageView.image]; 59 | imageViewMask.frame = CGRectMake(0, 0, size.width, size.height); 60 | self.previewImageView.layer.mask = imageViewMask.layer; 61 | 62 | [self.previewImageView mas_updateConstraints:^(MASConstraintMaker *make) { 63 | make.size.mas_offset(size); 64 | }]; 65 | } 66 | -(UIImageView *)previewImageView{ 67 | if (!_previewImageView) { 68 | _previewImageView = [[UIImageView alloc] init]; 69 | _previewImageView.layer.masksToBounds = YES; 70 | _previewImageView.userInteractionEnabled = YES; 71 | _previewImageView.contentMode = UIViewContentModeScaleAspectFill; 72 | } 73 | return _previewImageView; 74 | } 75 | -(UILabel *)placeLabel{ 76 | if (!_placeLabel) { 77 | _placeLabel = [[UILabel alloc] init]; 78 | _placeLabel.font = [UIFont systemFontOfSize:14]; 79 | _placeLabel.textColor = UIColorFromRGB(0xffffff); 80 | _placeLabel.backgroundColor = UIColorFromRGBA(0x000000, 0.7); 81 | _placeLabel.textAlignment = NSTextAlignmentCenter; 82 | } 83 | return _placeLabel; 84 | } 85 | @end 86 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLMessageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLMessageCell.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TLProjectMacro.h" 11 | #import "NSDate+Utils.h" 12 | 13 | #define SMAS(x) [self.constraints addObject:x] 14 | 15 | @interface TLMessageBubble : UIImageView 16 | 17 | @end 18 | 19 | @interface TLMessageCell : UITableViewCell 20 | @property(nonatomic,strong)TLMessageBubble *bubbleImageView; 21 | @property(nonatomic,strong)UIImageView *arrowImageView; 22 | @property(nonatomic,strong)UIView *statusView; 23 | @property(nonatomic,strong)UIActivityIndicatorView *activityIndicator; 24 | @property(nonatomic,strong)UIButton *retryBtn; 25 | @property(nonatomic,strong)UIImageView *avatarImageView; 26 | @property(nonatomic,strong)NSMutableArray *constraints; 27 | @property(nonatomic,strong)UILabel *dateTimeLabel; 28 | 29 | @property(nonatomic,strong)RCMessage *message; 30 | 31 | @property(nonatomic, copy)void(^reSendAction)(RCMessage *message); 32 | @property(nonatomic,strong)void(^clickAvatar)(RCMessageDirection msgDirection); 33 | 34 | -(void)updateDirection:(RCMessageDirection)direction; 35 | -(void)updateMessage:(RCMessage *)message showDate:(BOOL)showDate; 36 | -(void)setMsgStatus:(RCSentStatus)msgStatus; 37 | @end 38 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLMessageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLMessageCell.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLMessageCell.h" 10 | 11 | @interface TLMessageCell () 12 | @end 13 | 14 | @implementation TLMessageCell 15 | -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 16 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 17 | self.backgroundColor = [UIColor clearColor]; 18 | self.constraints = [NSMutableArray array]; 19 | 20 | self.selectionStyle = UITableViewCellSelectionStyleNone; 21 | 22 | [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) { 23 | make.edges.equalTo(self).priorityHigh(); 24 | }]; 25 | 26 | [self.contentView addSubview:self.dateTimeLabel]; 27 | [self.dateTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) { 28 | make.top.equalTo(self.contentView.mas_top).offset(0); 29 | make.left.equalTo(self.contentView.mas_left).offset(0); 30 | make.right.equalTo(self.contentView.mas_right).offset(0); 31 | make.height.mas_offset(0); 32 | }]; 33 | 34 | [self.contentView addSubview:self.avatarImageView]; 35 | [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { 36 | make.top.equalTo(self.dateTimeLabel.mas_bottom).offset(8); 37 | make.size.mas_offset(CGSizeMake(30, 30)); 38 | SMAS(make.left.equalTo(self.contentView.mas_left).offset(10)); 39 | }]; 40 | 41 | [self.contentView addSubview:self.bubbleImageView]; 42 | [self.bubbleImageView mas_makeConstraints:^(MASConstraintMaker *make) { 43 | make.top.equalTo(self.avatarImageView.mas_top).offset(0); 44 | make.bottom.equalTo(self.contentView.mas_bottom).offset(-8).priorityHigh(); 45 | SMAS(make.left.equalTo(self.avatarImageView.mas_right).offset(7)); 46 | SMAS(make.right.lessThanOrEqualTo(self.contentView.mas_right).offset(-78)); 47 | }]; 48 | 49 | [self.contentView addSubview:self.statusView]; 50 | [self.statusView mas_makeConstraints:^(MASConstraintMaker *make) { 51 | make.centerY.equalTo(self.bubbleImageView.mas_centerY).offset(0); 52 | make.size.mas_offset(CGSizeMake(38, 38)); 53 | SMAS(make.left.equalTo(self.bubbleImageView.mas_right).offset(0)); 54 | }]; 55 | 56 | [self.statusView addSubview:self.activityIndicator]; 57 | [self.activityIndicator mas_makeConstraints:^(MASConstraintMaker *make) { 58 | make.centerY.equalTo(self.statusView.mas_centerY).offset(0); 59 | make.centerX.equalTo(self.statusView.mas_centerX).offset(0); 60 | make.size.mas_offset(CGSizeMake(10, 10)); 61 | }]; 62 | 63 | [self.statusView addSubview:self.retryBtn]; 64 | [self.retryBtn mas_makeConstraints:^(MASConstraintMaker *make) { 65 | make.centerY.equalTo(self.statusView.mas_centerY).offset(0); 66 | make.centerX.equalTo(self.statusView.mas_centerX).offset(0); 67 | }]; 68 | 69 | UITapGestureRecognizer *avatarTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTapAction)]; 70 | [self.avatarImageView addGestureRecognizer:avatarTap]; 71 | } 72 | return self; 73 | } 74 | -(void)updateDirection:(RCMessageDirection)direction{ 75 | [self removeAllConstraints]; 76 | 77 | NSString *bubbleNamed = direction == MessageDirection_RECEIVE ? @"chat_from_bg_normal" : @"chat_to_bg_normal"; 78 | self.bubbleImageView.image = [[UIImage imageNamed:bubbleNamed] stretchableImageWithLeftCapWidth:15 topCapHeight:25]; 79 | 80 | [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { 81 | if (direction == MessageDirection_RECEIVE) { 82 | SMAS(make.left.equalTo(self.contentView.mas_left).offset(10)); 83 | }else{ 84 | SMAS(make.right.equalTo(self.contentView.mas_right).offset(-10)); 85 | } 86 | }]; 87 | 88 | [self.bubbleImageView mas_makeConstraints:^(MASConstraintMaker *make) { 89 | if (direction == MessageDirection_RECEIVE) { 90 | SMAS(make.left.equalTo(self.avatarImageView.mas_right).offset(7)); 91 | SMAS(make.right.lessThanOrEqualTo(self.contentView.mas_right).offset(-78)); 92 | }else{ 93 | SMAS(make.right.equalTo(self.avatarImageView.mas_left).offset(-7)); 94 | SMAS(make.left.greaterThanOrEqualTo(self.contentView.mas_left).offset(78)); 95 | } 96 | }]; 97 | 98 | [self.statusView mas_makeConstraints:^(MASConstraintMaker *make) { 99 | if (direction == MessageDirection_RECEIVE) { 100 | SMAS(make.left.equalTo(self.bubbleImageView.mas_right).offset(0)); 101 | }else{ 102 | SMAS(make.right.equalTo(self.bubbleImageView.mas_left).offset(0)); 103 | } 104 | }]; 105 | } 106 | -(void)updateDate:(long long)date showDate:(BOOL)showDate{ 107 | if (!showDate) { 108 | [self.dateTimeLabel mas_updateConstraints:^(MASConstraintMaker *make) { 109 | make.height.mas_offset(0); 110 | }]; 111 | self.dateTimeLabel.hidden = YES; 112 | return; 113 | } 114 | 115 | self.dateTimeLabel.hidden = NO; 116 | NSDate *lastDate = [NSDate dateWithTimeIntervalSince1970:date / 1000]; 117 | NSString *dateStr; //年月日 118 | 119 | if ([lastDate year] == [[NSDate date] year]) { 120 | NSInteger days = [NSDate daysOffsetBetweenStartDate:lastDate endDate:[NSDate date]]; 121 | dateStr = days <= 2 ? [lastDate stringYearMonthDayCompareToday] : [lastDate stringMonthDay]; 122 | }else{ 123 | dateStr = [lastDate stringYearMonthDay]; 124 | } 125 | 126 | [self.dateTimeLabel mas_updateConstraints:^(MASConstraintMaker *make) { 127 | make.height.mas_offset(18); 128 | }]; 129 | 130 | self.dateTimeLabel.text = [NSString stringWithFormat:@"%@ %02d:%02d",dateStr,(int)[lastDate hour],(int)[lastDate minute]]; 131 | } 132 | -(void)updateMessage:(RCMessage *)message showDate:(BOOL)showDate{ 133 | if (!message.content) { 134 | return; 135 | } 136 | [self updateDirection:message.messageDirection]; 137 | [self updateDate:message.sentTime showDate:showDate]; 138 | self.message = message; 139 | self.msgStatus = message.sentStatus; 140 | } 141 | -(void)setMsgStatus:(RCSentStatus)msgStatus{ 142 | if (msgStatus == SentStatus_SENT) { 143 | self.retryBtn.hidden = YES; 144 | self.activityIndicator.hidden = YES; 145 | } 146 | self.retryBtn.hidden = msgStatus != SentStatus_FAILED; 147 | if (msgStatus == SentStatus_SENDING) { 148 | [self.activityIndicator startAnimating]; 149 | self.activityIndicator.hidden = NO; 150 | }else{ 151 | [self.activityIndicator stopAnimating]; 152 | self.activityIndicator.hidden = YES; 153 | } 154 | } 155 | -(void)avatarTapAction { 156 | self.clickAvatar(self.message.messageDirection); 157 | } 158 | -(void)removeAllConstraints{ 159 | for (MASConstraint *constraint in self.constraints) { 160 | [constraint uninstall]; 161 | } 162 | } 163 | -(void)retryBtnAction{ 164 | if (self.reSendAction) self.reSendAction(self.message); 165 | } 166 | -(BOOL)canBecomeFirstResponder{ 167 | return YES; 168 | } 169 | -(BOOL)canResignFirstResponder{ 170 | return YES; 171 | } 172 | 173 | -(UILabel *)dateTimeLabel{ 174 | if (!_dateTimeLabel) { 175 | _dateTimeLabel = [[UILabel alloc] init]; 176 | _dateTimeLabel.textColor = UIColorFromRGB(0x999999); 177 | _dateTimeLabel.font = [UIFont systemFontOfSize:9]; 178 | _dateTimeLabel.textAlignment = NSTextAlignmentCenter; 179 | } 180 | return _dateTimeLabel; 181 | } 182 | -(UIImageView *)avatarImageView{ 183 | if (!_avatarImageView) { 184 | _avatarImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"chatfrom_doctor_icon"]]; 185 | _avatarImageView.layer.cornerRadius = 15; 186 | _avatarImageView.layer.masksToBounds = YES; 187 | _avatarImageView.userInteractionEnabled = YES; 188 | } 189 | return _avatarImageView; 190 | } 191 | -(UIView *)statusView{ 192 | if (!_statusView) { 193 | _statusView = [[UIView alloc] init]; 194 | } 195 | return _statusView; 196 | } 197 | -(UIButton *)retryBtn{ 198 | if (!_retryBtn) { 199 | _retryBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 200 | [_retryBtn setImage:[UIImage imageNamed:@"news_failinsend"] forState:UIControlStateNormal]; 201 | [_retryBtn addTarget:self action:@selector(retryBtnAction) forControlEvents:UIControlEventTouchUpInside]; 202 | } 203 | return _retryBtn; 204 | } 205 | -(UIActivityIndicatorView *)activityIndicator{ 206 | if (!_activityIndicator) { 207 | _activityIndicator = [[UIActivityIndicatorView alloc] init]; 208 | _activityIndicator.hidesWhenStopped = YES; 209 | _activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite; 210 | _activityIndicator.color = UIColorFromRGB(0x999999); 211 | } 212 | return _activityIndicator; 213 | } 214 | -(TLMessageBubble *)bubbleImageView{ 215 | if (!_bubbleImageView) { 216 | _bubbleImageView = [[TLMessageBubble alloc] init]; 217 | _bubbleImageView.userInteractionEnabled = YES; 218 | } 219 | return _bubbleImageView; 220 | } 221 | -(UIImageView *)arrowImageView{ 222 | if (!_arrowImageView) { 223 | _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"sharp"]]; 224 | } 225 | return _arrowImageView; 226 | } 227 | @end 228 | 229 | 230 | 231 | @implementation TLMessageBubble 232 | -(BOOL)canBecomeFirstResponder{ 233 | return YES; 234 | } 235 | @end 236 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLPhotoMessageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoMessageCell.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLMessageCell.h" 10 | 11 | @interface TLPhotoMessageCell : TLMessageCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLPhotoMessageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoMessageCell.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLPhotoMessageCell.h" 10 | #import 11 | #import "TLPhotoBrowser.h" 12 | 13 | static CGFloat fitImgWidth = 150; 14 | static CGFloat fitImgHeight = 150; 15 | 16 | 17 | @interface TLPhotoMessageCell () 18 | @property(nonatomic,strong)UIImageView *photoImageView; 19 | @property(nonatomic,strong)UIImageView *imageViewMask; 20 | @end 21 | 22 | @implementation TLPhotoMessageCell 23 | -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 24 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 25 | [self.bubbleImageView addSubview:self.photoImageView]; 26 | [self.photoImageView mas_makeConstraints:^(MASConstraintMaker *make) { 27 | make.edges.equalTo(self.bubbleImageView); 28 | }]; 29 | 30 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didClickImage)]; 31 | [self.photoImageView addGestureRecognizer:tap]; 32 | 33 | } 34 | return self; 35 | } 36 | -(void)didClickImage{ 37 | RCImageMessage *imgMessage = (RCImageMessage *)self.message.content; 38 | 39 | if (imgMessage.originalImage) { 40 | [TLPhotoBrowser showOriginalImage:imgMessage.originalImage]; 41 | }else{ 42 | [self.photoImageView sd_setImageWithURL:[NSURL URLWithString:imgMessage.imageUrl] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { 43 | [TLPhotoBrowser showOriginalImage:image]; 44 | }]; 45 | } 46 | } 47 | 48 | -(void)updateMessage:(RCMessage *)message showDate:(BOOL)showDate{ 49 | [super updateMessage:message showDate:showDate]; 50 | RCImageMessage *imgMessage = (RCImageMessage *)message.content; 51 | self.photoImageView.image = imgMessage.thumbnailImage; 52 | 53 | CGSize imgSize = imgMessage.thumbnailImage.size; 54 | CGFloat scale = imgSize.width / imgSize.height; 55 | 56 | CGFloat newWidth = MAX(MIN(imgSize.width, fitImgWidth), fitImgWidth); 57 | CGFloat newHeight = MAX(MIN(imgSize.height, fitImgHeight), fitImgHeight); 58 | 59 | CGSize newSize = scale > 1 ? CGSizeMake(newWidth, newWidth / scale) : CGSizeMake(newHeight * scale, newHeight); 60 | 61 | self.imageViewMask.image = self.bubbleImageView.image; 62 | self.imageViewMask.frame = CGRectMake(0, 0, newSize.width, newSize.height); 63 | self.photoImageView.layer.mask = self.imageViewMask.layer; 64 | 65 | [self.photoImageView mas_updateConstraints:^(MASConstraintMaker *make) { 66 | make.size.mas_offset(newSize); 67 | }]; 68 | } 69 | 70 | -(UIImageView *)imageViewMask{ 71 | if (!_imageViewMask) { 72 | _imageViewMask = [[UIImageView alloc] init]; 73 | } 74 | return _imageViewMask; 75 | } 76 | -(UIImageView *)photoImageView{ 77 | if (!_photoImageView) { 78 | _photoImageView = [[UIImageView alloc] init]; 79 | _photoImageView.layer.masksToBounds = YES; 80 | _photoImageView.userInteractionEnabled = YES; 81 | _photoImageView.contentMode = UIViewContentModeScaleAspectFill; 82 | [_photoImageView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal]; 83 | } 84 | return _photoImageView; 85 | } 86 | @end 87 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLPhotoThumbCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoThumbCell.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/25. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface TLPhotoThumbCell : UICollectionViewCell 13 | @property(nonatomic,strong)PHAsset *item; 14 | @property(nonatomic,copy)BOOL (^selectBlock)(PHAsset *x); 15 | @end 16 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLPhotoThumbCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoThumbCell.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/25. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLPhotoThumbCell.h" 10 | #import "TLProjectMacro.h" 11 | #import "TLButton.h" 12 | #import "PHAsset+Extend.h" 13 | 14 | @interface TLPhotoThumbCell () 15 | @property(nonatomic,strong)UIImageView *imageView; 16 | @property(nonatomic,strong)TLButton *selectBtn; 17 | @property(nonatomic,strong)PHImageRequestOptions *options; 18 | @end 19 | 20 | @implementation TLPhotoThumbCell 21 | -(instancetype)initWithFrame:(CGRect)frame{ 22 | if (self = [super initWithFrame:frame]) { 23 | [self setupView]; 24 | } 25 | return self; 26 | } 27 | -(void)setupView{ 28 | [self.contentView addSubview:self.imageView]; 29 | [self.imageView mas_makeConstraints:^(MASConstraintMaker *make) { 30 | make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, 0, 0)); 31 | }]; 32 | 33 | [self.contentView addSubview:self.selectBtn]; 34 | [self.selectBtn mas_makeConstraints:^(MASConstraintMaker *make) { 35 | make.top.equalTo(self.contentView.mas_top).offset(5); 36 | make.right.equalTo(self.contentView.mas_right).offset(-5); 37 | }]; 38 | } 39 | -(void)setItem:(PHAsset *)item{ 40 | _item = item; 41 | self.selectBtn.selected = item.selected; 42 | [[PHImageManager defaultManager] requestImageForAsset:item targetSize:CGSizeMake(UI_SCREEN_HEIGHT / 4, UI_SCREEN_HEIGHT / 4) contentMode:PHImageContentModeDefault options:self.options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { 43 | self.imageView.image = result; 44 | }]; 45 | } 46 | -(void)selectAction:(UIButton *)sender{ 47 | self.item.selected = !sender.selected; 48 | if (self.selectBlock && self.selectBlock(self.item)){ 49 | sender.selected = !sender.selected; 50 | } 51 | } 52 | -(UIImageView *)imageView{ 53 | if (!_imageView) { 54 | _imageView = [[UIImageView alloc] init]; 55 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 56 | _imageView.clipsToBounds = YES; 57 | } 58 | return _imageView; 59 | } 60 | -(TLButton *)selectBtn{ 61 | if (!_selectBtn) { 62 | _selectBtn = [TLButton buttonWithType:UIButtonTypeCustom]; 63 | [_selectBtn setImage:[UIImage imageNamed:@"photopicker_state_normal"] forState:UIControlStateNormal]; 64 | [_selectBtn setImage:[UIImage imageNamed:@"photopicker_state_selected"] forState:UIControlStateSelected]; 65 | [_selectBtn addTarget:self action:@selector(selectAction:) forControlEvents:UIControlEventTouchUpInside]; 66 | } 67 | return _selectBtn; 68 | } 69 | -(PHImageRequestOptions *)options{ 70 | if (!_options) { 71 | _options = [[PHImageRequestOptions alloc] init]; 72 | } 73 | return _options; 74 | } 75 | @end 76 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLTextMessageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLTextMessageCell.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLMessageCell.h" 10 | 11 | @interface TLTextMessageCell : TLMessageCell 12 | @end 13 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLTextMessageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLTextMessageCell.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLTextMessageCell.h" 10 | 11 | @interface TLTextMessageCell () 12 | @property(nonatomic,strong)UILabel *messageLabel; 13 | @end 14 | 15 | @implementation TLTextMessageCell 16 | -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 17 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 18 | [self.bubbleImageView addSubview:self.messageLabel]; 19 | [self.messageLabel mas_makeConstraints:^(MASConstraintMaker *make) { 20 | make.edges.mas_offset(UIEdgeInsetsMake(10, 15, 10, 15)); 21 | }]; 22 | } 23 | return self; 24 | } 25 | -(void)updateDirection:(RCMessageDirection)direction{ 26 | [super updateDirection:direction]; 27 | [self.messageLabel mas_updateConstraints:^(MASConstraintMaker *make) { 28 | if (direction == MessageDirection_RECEIVE) { 29 | make.edges.mas_offset(UIEdgeInsetsMake(10, 15, 10, 10)); 30 | }else{ 31 | make.edges.mas_offset(UIEdgeInsetsMake(10, 10, 10, 15)); 32 | } 33 | }]; 34 | } 35 | -(void)updateMessage:(RCMessage *)message showDate:(BOOL)showDate{ 36 | [super updateMessage:message showDate:showDate]; 37 | 38 | RCTextMessage* textMessage = (RCTextMessage*)message.content; 39 | self.messageLabel.text = textMessage.content; 40 | } 41 | -(UILabel *)messageLabel{ 42 | if (!_messageLabel) { 43 | _messageLabel = [[UILabel alloc] init]; 44 | _messageLabel.font = [UIFont systemFontOfSize:15]; 45 | _messageLabel.textColor = UIColorFromRGB(0x333333); 46 | _messageLabel.numberOfLines = 0; 47 | } 48 | return _messageLabel; 49 | } 50 | @end 51 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLVoiceMessageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLVoiceMessageCell.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLMessageCell.h" 10 | 11 | @interface TLVoiceMessageCell : TLMessageCell 12 | @property(nonatomic,assign)BOOL isPlaying; 13 | @end 14 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Cell/TLVoiceMessageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLVoiceMessageCell.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLVoiceMessageCell.h" 10 | #import "TLVoicePlayer.h" 11 | 12 | @interface TLVoiceMessageCell () 13 | @property(nonatomic,strong)UIImageView *voicePlayingImageView; 14 | @property(nonatomic,strong)UILabel *voiceDurationLabel; 15 | @end 16 | 17 | @implementation TLVoiceMessageCell 18 | -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ 19 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 20 | [self.bubbleImageView mas_makeConstraints:^(MASConstraintMaker *make) { 21 | make.size.mas_offset(CGSizeMake(80, 40)); 22 | }]; 23 | 24 | [self.bubbleImageView addSubview:self.voicePlayingImageView]; 25 | [self.voicePlayingImageView mas_makeConstraints:^(MASConstraintMaker *make) { 26 | SMAS(make.left.equalTo(self.bubbleImageView.mas_left).offset(10)); 27 | make.centerY.equalTo(self.bubbleImageView.mas_centerY).offset(0); 28 | make.size.mas_offset(CGSizeMake(20, 20)); 29 | }]; 30 | 31 | [self.bubbleImageView addSubview:self.voiceDurationLabel]; 32 | [self.voiceDurationLabel mas_makeConstraints:^(MASConstraintMaker *make) { 33 | SMAS(make.right.equalTo(self.bubbleImageView.mas_right).offset(5)); 34 | make.centerY.equalTo(self.bubbleImageView.mas_centerY).offset(0); 35 | }]; 36 | 37 | self.bubbleImageView.userInteractionEnabled = YES; 38 | UITapGestureRecognizer *playTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playRecord)]; 39 | [self.bubbleImageView addGestureRecognizer:playTap]; 40 | } 41 | return self; 42 | } 43 | -(void)playRecord{ 44 | void (^didFinishPlay)(void) = ^(){ 45 | [self.voicePlayingImageView stopAnimating]; 46 | self.isPlaying = NO; 47 | [[TLVoicePlayer sharePlayer] endPlay]; 48 | }; 49 | 50 | if (!self.isPlaying) { 51 | [self.voicePlayingImageView startAnimating]; 52 | self.isPlaying = YES; 53 | [[TLVoicePlayer sharePlayer] playVoiceWithData:((RCVoiceMessage *)self.message.content).wavAudioData didFinish:didFinishPlay]; 54 | }else{ 55 | didFinishPlay(); 56 | } 57 | } 58 | -(void)updateDirection:(RCMessageDirection)direction{ 59 | [super updateDirection:direction]; 60 | 61 | [self.voicePlayingImageView mas_makeConstraints:^(MASConstraintMaker *make) { 62 | if (direction == MessageDirection_RECEIVE) { 63 | SMAS(make.left.equalTo(self.bubbleImageView.mas_left).offset(10)); 64 | }else{ 65 | SMAS(make.right.equalTo(self.bubbleImageView.mas_right).offset(-10)); 66 | } 67 | }]; 68 | 69 | [self.voiceDurationLabel mas_makeConstraints:^(MASConstraintMaker *make) { 70 | if (direction == MessageDirection_RECEIVE) { 71 | SMAS(make.right.equalTo(self.bubbleImageView.mas_right).offset(-10)); 72 | }else{ 73 | SMAS(make.left.equalTo(self.bubbleImageView.mas_left).offset(10)); 74 | } 75 | }]; 76 | 77 | self.voiceDurationLabel.textColor = direction == MessageDirection_RECEIVE ? UIColorFromRGB(0xa2a2a2) : UIColorFromRGB(0x4182b5); 78 | } 79 | -(void)updateMessage:(RCMessage *)message showDate:(BOOL)showDate{ 80 | [super updateMessage:message showDate:showDate]; 81 | 82 | self.voicePlayingImageView.animationImages = message.messageDirection == MessageDirection_RECEIVE ? @[[UIImage imageNamed:@"from_voice_1"],[UIImage imageNamed:@"from_voice_2"],[UIImage imageNamed:@"from_voice_3"]] : @[[UIImage imageNamed:@"to_voice_1"],[UIImage imageNamed:@"to_voice_2"],[UIImage imageNamed:@"to_voice_3"]]; 83 | 84 | self.voicePlayingImageView.image = message.messageDirection == MessageDirection_RECEIVE ? [UIImage imageNamed:@"from_voice"] : [UIImage imageNamed:@"to_voice"]; 85 | 86 | self.voiceDurationLabel.text = [NSString stringWithFormat:@"%ld''",((RCVoiceMessage *)message.content).duration]; 87 | } 88 | -(UIImageView *)voicePlayingImageView{ 89 | if (!_voicePlayingImageView) { 90 | _voicePlayingImageView = [[UIImageView alloc] init]; 91 | _voicePlayingImageView.userInteractionEnabled = YES; 92 | _voicePlayingImageView.animationDuration = 1; 93 | _voicePlayingImageView.animationRepeatCount = 0; 94 | } 95 | return _voicePlayingImageView; 96 | } 97 | -(UILabel *)voiceDurationLabel{ 98 | if (!_voiceDurationLabel) { 99 | _voiceDurationLabel = [[UILabel alloc] init]; 100 | _voiceDurationLabel.textColor = UIColorFromRGB(0x4182b5); 101 | _voiceDurationLabel.font = [UIFont systemFontOfSize:15]; 102 | } 103 | return _voiceDurationLabel; 104 | } 105 | @end 106 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLChatViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLChatViewController.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TLChatViewController : UIViewController 12 | @property(nonatomic,strong)UITableView *chatTableView; 13 | -(void)scrollToBottom; 14 | @end 15 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLChatViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLChatViewController.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLChatViewController.h" 10 | #import "TLProjectMacro.h" 11 | #import "TLTextMessageCell.h" 12 | #import "TLPhotoMessageCell.h" 13 | #import "TLVoiceMessageCell.h" 14 | #import "TLLocationMessageCell.h" 15 | #import "TLChatInputView.h" 16 | #import "TLRCManager.h" 17 | #import "APIDebug.h" 18 | 19 | @interface TLChatViewController () 20 | 21 | 24 | 25 | @property(nonatomic,strong)TLChatInputView *inputView; 26 | @property(nonatomic,strong)NSMutableArray *messages; 27 | @end 28 | 29 | @implementation TLChatViewController 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | 33 | [[TLRCManager shareManager] connectWithToken:@"jqAhQVLtdDAJsN+nYuGMg+SZkEN9cVfzRVKTg8tY0IOhDwJ1Cn3qxdbPXWTk4XVCfZLmci9yJ2QWgjEOhUtgXg=="]; 34 | [TLRCManager shareManager].delegate = self; 35 | 36 | self.view.backgroundColor = [UIColor whiteColor]; 37 | self.navigationController.navigationBar.barTintColor = [UIColor whiteColor]; 38 | 39 | [self.view addSubview:self.inputView]; 40 | [self.inputView mas_makeConstraints:^(MASConstraintMaker *make) { 41 | make.left.equalTo(self.view.mas_left).offset(0); 42 | make.right.equalTo(self.view.mas_right).offset(0); 43 | make.bottom.equalTo(self.view.mas_bottom).offset(0); 44 | make.height.mas_offset(@45); 45 | }]; 46 | 47 | [self.view addSubview:self.chatTableView]; 48 | [self.chatTableView mas_makeConstraints:^(MASConstraintMaker *make) { 49 | make.left.equalTo(self.view.mas_left).offset(0); 50 | make.right.equalTo(self.view.mas_right).offset(0); 51 | make.top.equalTo(self.mas_topLayoutGuideBottom).offset(0); 52 | make.bottom.equalTo(self.inputView.mas_top).offset(0); 53 | }]; 54 | 55 | weakifySelf; 56 | //发送消息回调 57 | self.inputView.sendMsgAction = ^(RCMessageContent *x){ 58 | strongifySelf; 59 | [self sendMessage:x]; 60 | }; 61 | 62 | //navbar上的一个APIDebug调试,可注释掉 63 | [APIDebug configWithVC:self]; 64 | } 65 | 66 | #pragma - mark tableviewDelegate 67 | 68 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 69 | RCMessage *msg = self.messages[indexPath.row]; 70 | RCMessage *lastMsg = [self lasetMsgWithIndex:indexPath.row]; 71 | 72 | TLMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:[self cellIdentifierWithMsg:msg]]; 73 | 74 | weakifySelf; 75 | cell.reSendAction = ^(RCMessage *msg){ 76 | strongifySelf; 77 | msg.sentStatus = SentStatus_SENDING; 78 | [self retrySendMessage:msg]; 79 | }; 80 | 81 | cell.clickAvatar = ^(RCMessageDirection msgDirection){ 82 | 83 | }; 84 | 85 | [cell updateMessage:msg showDate:(msg.sentTime - lastMsg.sentTime > 60 * 5 * 1000)]; 86 | 87 | return cell; 88 | } 89 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 90 | return self.messages.count; 91 | } 92 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 93 | RCMessage *msg = self.messages[indexPath.row]; 94 | RCMessage *lastMsg = [self lasetMsgWithIndex:indexPath.row]; 95 | CGFloat height = [tableView fd_heightForCellWithIdentifier:[self cellIdentifierWithMsg:msg] cacheByIndexPath:indexPath configuration:^(TLMessageCell *cell) { 96 | [cell updateMessage:msg showDate:(msg.sentTime - lastMsg.sentTime > 60 * 5 * 1000)]; 97 | }]; 98 | return height; 99 | } 100 | 101 | #pragma - mark RCManagerDelegate 102 | 103 | -(void)rcManagerReceiveMsg:(RCMessage *)msg{ 104 | msg.sentStatus = SentStatus_RECEIVED; 105 | dispatch_async(dispatch_get_main_queue(), ^{ 106 | [self insertMessage:msg]; 107 | }); 108 | } 109 | 110 | #pragma - mark private 111 | -(void)sendMessage:(id)message{ 112 | RCMessage *msg = [[RCMessage alloc] initWithType:ConversationType_PRIVATE targetId:@"111" direction:MessageDirection_SEND messageId:0 content:message]; 113 | 114 | msg.sentStatus = SentStatus_SENDING; 115 | msg.receivedStatus = ReceivedStatus_READ; 116 | [self insertMessage:msg]; 117 | [self retrySendMessage:msg]; 118 | } 119 | - (void)insertMessage:(RCMessage *)message{ 120 | if (!message.content) { 121 | return; 122 | } 123 | [self.messages addObject:message]; 124 | 125 | [self.chatTableView insertRowsAtIndexPaths:@[[self lastMessageIndexPath]] withRowAnimation:UITableViewRowAnimationFade]; 126 | 127 | [self.chatTableView scrollToRowAtIndexPath:[self lastMessageIndexPath] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; 128 | } 129 | - (void)retrySendMessage:(RCMessage *)message{ 130 | [[TLRCManager shareManager] sendMessage:message successBlock:^(RCMessage *x){ 131 | x.sentStatus = SentStatus_SENT; 132 | [self updateCellStatusWithMsg:x]; 133 | } failedBlock:^(RCMessage *x){ 134 | x.sentStatus = SentStatus_FAILED; 135 | [self updateCellStatusWithMsg:x]; 136 | }]; 137 | } 138 | -(void)updateCellStatusWithMsg:(RCMessage *)msg{ 139 | NSInteger index = [self.messages indexOfObject:msg]; 140 | TLMessageCell *cell = [self.chatTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]]; 141 | [cell setMsgStatus:msg.sentStatus]; 142 | } 143 | -(void)scrollToBottom{ 144 | if (self.messages.count) { 145 | [self.chatTableView scrollToRowAtIndexPath:[self lastMessageIndexPath] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; 146 | } 147 | } 148 | -(NSIndexPath *)lastMessageIndexPath{ 149 | return [NSIndexPath indexPathForItem:self.messages.count - 1 inSection:0]; 150 | } 151 | -(RCMessage *)lasetMsgWithIndex:(NSInteger)index{ 152 | return index > 0 ? self.messages[index - 1] : nil; 153 | } 154 | -(NSString *)cellIdentifierWithMsg:(RCMessage *)msg{ 155 | NSDictionary *dic = @{@"RCTextMessage":@"textcell", 156 | @"RCVoiceMessage":@"voicecell", 157 | @"RCImageMessage":@"photocell", 158 | @"RCLocationMessage":@"locationcell"}; 159 | return dic[NSStringFromClass([msg.content class])]; 160 | } 161 | #pragma - mark getter 162 | -(NSMutableArray *)messages{ 163 | if (!_messages) { 164 | _messages = [NSMutableArray array]; 165 | } 166 | return _messages; 167 | } 168 | -(UITableView *)chatTableView{ 169 | if (!_chatTableView) { 170 | _chatTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 171 | _chatTableView.delegate = self; 172 | _chatTableView.dataSource = self; 173 | _chatTableView.backgroundColor = UIColorFromRGB(0xebebeb); 174 | _chatTableView.separatorStyle = UITableViewCellSeparatorStyleNone; 175 | _chatTableView.backgroundColor = UIColorFromRGB(0xf8f8f8); 176 | _chatTableView.separatorColor = UIColorFromRGB(0xeeeeee); 177 | [_chatTableView registerClass:[TLTextMessageCell class] forCellReuseIdentifier:@"textcell"]; 178 | [_chatTableView registerClass:[TLPhotoMessageCell class] forCellReuseIdentifier:@"photocell"]; 179 | [_chatTableView registerClass:[TLVoiceMessageCell class] forCellReuseIdentifier:@"voicecell"]; 180 | [_chatTableView registerClass:[TLLocationMessageCell class] forCellReuseIdentifier:@"locationcell"]; 181 | } 182 | return _chatTableView; 183 | } 184 | -(TLChatInputView *)inputView{ 185 | if (!_inputView) { 186 | _inputView = [[TLChatInputView alloc] initWithChatVc:self]; 187 | } 188 | return _inputView; 189 | } 190 | - (void)didReceiveMemoryWarning { 191 | [super didReceiveMemoryWarning]; 192 | // Dispose of any resources that can be recreated. 193 | } 194 | @end 195 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLLocationViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLLocationViewController.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/23. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLProjectMacro.h" 10 | 11 | @protocol TLLocationViewControllerDelegate 12 | -(void)locationViewControllerSendMsg:(RCLocationMessage *)msg; 13 | @end 14 | 15 | @interface TLLocationViewController : UIViewController 16 | -(instancetype)initWithDelegate:(id )delegate; 17 | @property(nonatomic,assign)id delegate; 18 | @end 19 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLLocationViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLLocationViewController.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/23. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLLocationViewController.h" 10 | #import 11 | 12 | @interface TLLocationViewController () 13 | 17 | @property(nonatomic,strong)MKMapView *mapView; 18 | @property(nonatomic,strong)CLLocationManager *locationManager; 19 | @property(nonatomic,strong)MKUserLocation *userLocation; 20 | @property(nonatomic,strong)MKPointAnnotation *annotation; 21 | @property(nonatomic,strong)UITableView *listTableView; 22 | @property(nonatomic,strong)NSMutableArray *dataSource; 23 | @end 24 | 25 | @implementation TLLocationViewController 26 | -(instancetype)initWithDelegate:(id)delegate{ 27 | if (self = [super init]) { 28 | self.delegate = delegate; 29 | } 30 | return self; 31 | } 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | 35 | [self configNavBar]; 36 | 37 | [self.view addSubview:self.mapView]; 38 | [self.mapView mas_makeConstraints:^(MASConstraintMaker *make) { 39 | make.top.equalTo(self.mas_topLayoutGuideBottom).offset(0); 40 | make.left.equalTo(self.view.mas_left).offset(0); 41 | make.right.equalTo(self.view.mas_right).offset(0); 42 | make.height.mas_offset(@300); 43 | }]; 44 | 45 | [self.view addSubview:self.listTableView]; 46 | [self.listTableView mas_makeConstraints:^(MASConstraintMaker *make) { 47 | make.left.equalTo(self.view.mas_left).offset(0); 48 | make.right.equalTo(self.view.mas_right).offset(0); 49 | make.top.equalTo(self.mapView.mas_bottom).offset(0); 50 | make.bottom.equalTo(self.view.mas_bottom).offset(0); 51 | }]; 52 | 53 | [self.locationManager requestWhenInUseAuthorization]; 54 | 55 | if([CLLocationManager locationServicesEnabled]){ 56 | [self.locationManager startUpdatingLocation]; 57 | } 58 | } 59 | - (void)configNavBar { 60 | self.navigationController.navigationBar.barTintColor = [UIColor whiteColor]; 61 | 62 | UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(dismiss)]; 63 | backItem.tintColor = [UIColor grayColor]; 64 | self.navigationItem.leftBarButtonItem = backItem; 65 | 66 | UIBarButtonItem *sendItem = [[UIBarButtonItem alloc] initWithTitle:@"发送" style:UIBarButtonItemStylePlain target:self action:@selector(sendLocationMsg)]; 67 | sendItem.tintColor = [UIColor grayColor]; 68 | self.navigationItem.rightBarButtonItem = sendItem; 69 | self.navigationController.navigationBar.tintColor = [UIColor whiteColor]; 70 | } 71 | - (void)dismiss { 72 | [self.navigationController popViewControllerAnimated:YES]; 73 | } 74 | - (void)sendLocationMsg{ 75 | if (!self.userLocation) { 76 | return; 77 | } 78 | 79 | [self.navigationController popViewControllerAnimated:YES]; 80 | 81 | UIImage *image = [self snapshot:self.mapView]; 82 | RCLocationMessage *msg = [RCLocationMessage messageWithLocationImage:image location:self.userLocation.location.coordinate locationName:self.userLocation.subtitle]; 83 | if ([self.delegate respondsToSelector:@selector(locationViewControllerSendMsg:)]) { 84 | [self.delegate locationViewControllerSendMsg:msg]; 85 | } 86 | } 87 | - (UIImage *)snapshot:(UIView *)view{ 88 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 0); 89 | [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; 90 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 91 | UIGraphicsEndImageContext(); 92 | 93 | return image; 94 | } 95 | #pragma - mark tableviewDelegate 96 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 97 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 98 | CLPlacemark *item = self.dataSource[indexPath.row]; 99 | cell.textLabel.text = item.name; 100 | return cell; 101 | } 102 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 103 | return self.dataSource.count; 104 | } 105 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 106 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 107 | [self sendLocationMsg]; 108 | } 109 | #pragma mark – CLLocationManagerDelegate 110 | - (void)locationManager:(CLLocationManager *)manager 111 | didUpdateToLocation:(CLLocation *)newLocation 112 | fromLocation:(CLLocation *)oldLocation{ 113 | 114 | MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 2000 ,2000); 115 | 116 | if (!oldLocation) { 117 | MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:region]; 118 | [self.mapView setRegion:adjustedRegion animated:YES]; 119 | 120 | } 121 | } 122 | - (void)locationManager:(CLLocationManager *)manager 123 | didFailWithError:(NSError *)error{ 124 | 125 | } 126 | #pragma - mark mapviewDelegate 127 | - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation { 128 | CLGeocoder *geocoder = [[CLGeocoder alloc] init]; 129 | [geocoder reverseGeocodeLocation:userLocation.location completionHandler:^(NSArray *placemarks, NSError *error) { 130 | if (placemarks.count == 0 || error) { 131 | NSLog(@"找不到该位置"); 132 | return; 133 | } 134 | // 当前地标 135 | CLPlacemark *pm = [placemarks firstObject]; 136 | // 区域名称 137 | userLocation.title = pm.locality; 138 | // 详细名称 139 | userLocation.subtitle = pm.name; 140 | 141 | self.userLocation = userLocation; 142 | 143 | [self.dataSource removeAllObjects]; 144 | [self.dataSource addObject:pm]; 145 | [self.listTableView reloadData]; 146 | }]; 147 | } 148 | -(MKMapView *)mapView{ 149 | if (!_mapView) { 150 | _mapView = [[MKMapView alloc] init]; 151 | _mapView.mapType = MKMapTypeStandard; 152 | _mapView.showsUserLocation = YES; 153 | _mapView.delegate = self; 154 | } 155 | return _mapView; 156 | } 157 | -(CLLocationManager *)locationManager{ 158 | if (!_locationManager) { 159 | _locationManager = [[CLLocationManager alloc] init]; 160 | _locationManager.distanceFilter = kCLDistanceFilterNone;//距离筛选器,单位米(移动多少米才回调更新) 161 | _locationManager.desiredAccuracy = kCLLocationAccuracyBest; //精确度 162 | _locationManager.delegate = self; 163 | } 164 | return _locationManager; 165 | } 166 | -(MKPointAnnotation *)annotation{ 167 | if (!_annotation) { 168 | _annotation = [[MKPointAnnotation alloc] init]; 169 | } 170 | return _annotation; 171 | } 172 | -(UITableView *)listTableView{ 173 | if (!_listTableView) { 174 | _listTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 175 | _listTableView.delegate = self; 176 | _listTableView.dataSource = self; 177 | _listTableView.backgroundColor = UIColorFromRGB(0xebebeb); 178 | _listTableView.backgroundColor = UIColorFromRGB(0xf8f8f8); 179 | _listTableView.separatorColor = UIColorFromRGB(0xeeeeee); 180 | [_listTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; 181 | } 182 | return _listTableView; 183 | } 184 | -(NSMutableArray *)dataSource{ 185 | if (!_dataSource) { 186 | _dataSource = [NSMutableArray array]; 187 | } 188 | return _dataSource; 189 | } 190 | - (void)didReceiveMemoryWarning { 191 | [super didReceiveMemoryWarning]; 192 | // Dispose of any resources that can be recreated. 193 | } 194 | @end 195 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLPhotoPickerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoPickerViewController.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/25. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol TLPhotoPickerDelegate 13 | -(void)didSendPhotos:(NSArray *)photos; 14 | @end 15 | 16 | @interface TLPhotoPickerViewController : UIViewController 17 | @property(nonatomic,assign)id delegate; 18 | -(instancetype)initWithDelegate:(id )delegate; 19 | -(void)sendPhotoAction; 20 | @end 21 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLPhotoPickerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoPickerViewController.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/25. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLPhotoPickerViewController.h" 10 | #import "TLProjectMacro.h" 11 | #import "TLPhotoThumbCell.h" 12 | #import "TLPhotoPreviewViewController.h" 13 | #import "PHAsset+Extend.h" 14 | 15 | @interface TLPhotoPickerViewController () 16 | 17 | 20 | 21 | @property(nonatomic,strong)UICollectionView *photoCollectionView; 22 | @property(nonatomic,strong)UICollectionViewFlowLayout *colletionLayout; 23 | @property(nonatomic,strong)UIView *bottomView; 24 | @property(nonatomic,strong)UIView *line; 25 | @property(nonatomic,strong)UIButton *previewBtn; 26 | @property(nonatomic,strong)UIButton *sendBtn; 27 | @property(nonatomic,strong)TLCountLabel *countLabel; 28 | @property(nonatomic,strong)NSMutableArray *dataSource; 29 | @property(nonatomic,strong)NSMutableArray *selectPhotos; 30 | @end 31 | 32 | @implementation TLPhotoPickerViewController 33 | -(void)viewWillAppear:(BOOL)animated{ 34 | [super viewWillAppear:animated]; 35 | [self.photoCollectionView reloadData]; 36 | } 37 | -(instancetype)initWithDelegate:(id)delegate{ 38 | if (self = [super init]) { 39 | self.delegate = delegate; 40 | } 41 | return self; 42 | } 43 | - (void)viewDidLoad { 44 | [super viewDidLoad]; 45 | self.title = @"照相机胶卷"; 46 | self.view.backgroundColor = [UIColor whiteColor]; 47 | 48 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancel)]; 49 | 50 | [self.view addSubview:self.photoCollectionView]; 51 | [self.photoCollectionView mas_makeConstraints:^(MASConstraintMaker *make) { 52 | make.top.equalTo(self.view.mas_top).offset(0); 53 | make.left.equalTo(self.view.mas_left).offset(0); 54 | make.right.equalTo(self.view.mas_right).offset(0); 55 | }]; 56 | 57 | [self.view addSubview:self.bottomView]; 58 | [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) { 59 | make.top.equalTo(self.photoCollectionView.mas_bottom).offset(0); 60 | make.left.equalTo(self.view.mas_left).offset(0); 61 | make.right.equalTo(self.view.mas_right).offset(0); 62 | make.bottom.equalTo(self.view.mas_bottom).offset(0); 63 | make.height.mas_offset(@40); 64 | }]; 65 | 66 | [self.bottomView addSubview:self.line]; 67 | [self.line mas_makeConstraints:^(MASConstraintMaker *make) { 68 | make.left.equalTo(self.bottomView.mas_left).offset(0); 69 | make.right.equalTo(self.bottomView.mas_right).offset(0); 70 | make.top.equalTo(self.bottomView.mas_top).offset(0); 71 | make.height.mas_offset(@0.5); 72 | }]; 73 | 74 | [self.bottomView addSubview:self.previewBtn]; 75 | [self.previewBtn mas_makeConstraints:^(MASConstraintMaker *make) { 76 | make.left.equalTo(self.bottomView.mas_left).offset(0); 77 | make.top.equalTo(self.bottomView.mas_top).offset(0); 78 | make.bottom.equalTo(self.bottomView.mas_bottom).offset(0); 79 | make.width.mas_offset(@50); 80 | }]; 81 | 82 | [self.bottomView addSubview:self.sendBtn]; 83 | [self.sendBtn mas_makeConstraints:^(MASConstraintMaker *make) { 84 | make.right.equalTo(self.bottomView.mas_right).offset(0); 85 | make.top.equalTo(self.bottomView.mas_top).offset(0); 86 | make.bottom.equalTo(self.bottomView.mas_bottom).offset(0); 87 | make.width.mas_offset(@50); 88 | }]; 89 | 90 | [self.bottomView addSubview:self.countLabel]; 91 | [self.countLabel mas_makeConstraints:^(MASConstraintMaker *make) { 92 | make.right.equalTo(self.sendBtn.mas_left).offset(0); 93 | make.centerY.equalTo(self.sendBtn.mas_centerY).offset(0); 94 | }]; 95 | 96 | [self loadPhotos]; 97 | } 98 | - (void)cancel{ 99 | [self.navigationController dismissViewControllerAnimated:YES completion:nil]; 100 | } 101 | - (void)loadPhotos{ 102 | void (^reloadPhoto)(void) = ^{ 103 | PHFetchOptions *options = [[PHFetchOptions alloc] init]; 104 | options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; 105 | PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options]; 106 | for (NSInteger i = 0; i < assetsFetchResults.count; i++) { 107 | PHAsset *asset = assetsFetchResults[i]; 108 | if (asset.mediaType == PHAssetMediaTypeImage) { 109 | [self.dataSource addObject:asset]; 110 | } 111 | } 112 | dispatch_async(dispatch_get_main_queue(), ^{ 113 | [self.photoCollectionView reloadData]; 114 | }); 115 | }; 116 | 117 | if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusNotDetermined) { 118 | [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { 119 | if (status == PHAuthorizationStatusAuthorized) { 120 | reloadPhoto(); 121 | }else{ 122 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"在设置中允许照片访问" preferredStyle:UIAlertControllerStyleAlert]; 123 | [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]]; 124 | [self presentViewController:alert animated:YES completion:nil]; 125 | } 126 | }]; 127 | }else{ 128 | reloadPhoto(); 129 | } 130 | } 131 | -(void)sendPhotoAction{ 132 | [self dismissViewControllerAnimated:YES completion:^{ 133 | if ([self.delegate respondsToSelector:@selector(didSendPhotos:)]) { 134 | [self.delegate didSendPhotos:self.selectPhotos]; 135 | } 136 | }]; 137 | } 138 | -(void)previewAction { 139 | [self showPreviewWithSelectedAsset:nil]; 140 | } 141 | -(void)showPreviewWithSelectedAsset:(PHAsset *)asset { 142 | TLPhotoPreviewViewController *vc = [[TLPhotoPreviewViewController alloc] initWithSelectedAsset:asset assets:self.dataSource]; 143 | vc.delegate = self; 144 | [self.navigationController pushViewController:vc animated:YES]; 145 | } 146 | -(void)selectedPhoto:(PHAsset *)photo{ 147 | if ([self.selectPhotos indexOfObject:photo] != NSIntegerMax) { 148 | return; 149 | } 150 | [self.selectPhotos addObject:photo]; 151 | [self updateSelectedCount]; 152 | } 153 | -(void)removePhoto:(PHAsset *)photo{ 154 | if ([self.selectPhotos indexOfObject:photo] != NSIntegerMax) { 155 | [self.selectPhotos removeObject:photo]; 156 | } 157 | [self updateSelectedCount]; 158 | } 159 | -(void)updateSelectedCount{ 160 | self.countLabel.text = @(self.selectPhotos.count).stringValue; 161 | self.countLabel.hidden = !self.selectPhotos.count; 162 | self.sendBtn.enabled = self.selectPhotos.count; 163 | self.previewBtn.enabled = self.selectPhotos.count; 164 | } 165 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 166 | TLPhotoThumbCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; 167 | cell.item = self.dataSource[indexPath.row]; 168 | weakifySelf; 169 | cell.selectBlock = ^(PHAsset *x){ 170 | strongifySelf; 171 | if (self.selectPhotos.count > 8 && x.selected) { 172 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"最多只能选择9张照片" preferredStyle:UIAlertControllerStyleAlert]; 173 | [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]]; 174 | [self presentViewController:alert animated:YES completion:nil]; 175 | return NO; 176 | }else{ 177 | x.selected ? [self.selectPhotos addObject:x] : [self.selectPhotos removeObject:x]; 178 | [self updateSelectedCount]; 179 | return YES; 180 | } 181 | }; 182 | return cell; 183 | } 184 | -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 185 | PHAsset *item = self.dataSource[indexPath.row]; 186 | [self showPreviewWithSelectedAsset:item]; 187 | } 188 | -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 189 | return self.dataSource.count; 190 | } 191 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 192 | return UIEdgeInsetsMake(5, 5, 5, 5); 193 | } 194 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 195 | CGFloat width = (UI_SCREEN_WIDTH - 25) / 4; 196 | return CGSizeMake(width, width); 197 | } 198 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { 199 | return 5.0; 200 | } 201 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { 202 | return 5.0; 203 | } 204 | -(void)reloadPhotos{ 205 | [self.photoCollectionView reloadData]; 206 | } 207 | -(void)sendPhotos{ 208 | [self sendPhotoAction]; 209 | } 210 | -(NSMutableArray *)dataSource{ 211 | if (!_dataSource) { 212 | _dataSource = [NSMutableArray array]; 213 | } 214 | return _dataSource; 215 | } 216 | -(NSMutableArray *)selectPhotos{ 217 | if (!_selectPhotos) { 218 | _selectPhotos = [NSMutableArray array]; 219 | } 220 | return _selectPhotos; 221 | } 222 | -(UIView *)bottomView{ 223 | if (!_bottomView) { 224 | _bottomView = [[UIView alloc] init]; 225 | 226 | } 227 | return _bottomView; 228 | } 229 | -(UIView *)line{ 230 | if (!_line) { 231 | _line = [[UIView alloc] init]; 232 | _line.backgroundColor = UIColorFromRGB(0xcccccc); 233 | } 234 | return _line; 235 | } 236 | -(UIButton *)previewBtn{ 237 | if (!_previewBtn) { 238 | _previewBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 239 | [_previewBtn setTitleColor:UIColorFromRGB(0x999999) forState:UIControlStateNormal]; 240 | [_previewBtn setTitleColor:UIColorFromRGB(0xcccccc) forState:UIControlStateDisabled]; 241 | [_previewBtn setTitle:@"预览" forState:UIControlStateNormal]; 242 | [_previewBtn addTarget:self action:@selector(previewAction) forControlEvents:UIControlEventTouchUpInside]; 243 | _previewBtn.titleLabel.font = [UIFont systemFontOfSize:15]; 244 | _previewBtn.enabled = NO; 245 | } 246 | return _previewBtn; 247 | } 248 | -(UIButton *)sendBtn{ 249 | if (!_sendBtn) { 250 | _sendBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 251 | [_sendBtn setTitleColor:UIColorFromRGB(0x007AFF) forState:UIControlStateNormal]; 252 | [_sendBtn setTitleColor:UIColorFromRGB(0xa3cdff) forState:UIControlStateDisabled]; 253 | [_sendBtn setTitle:@"发送" forState:UIControlStateNormal]; 254 | _sendBtn.titleLabel.font = [UIFont systemFontOfSize:15]; 255 | [_sendBtn addTarget:self action:@selector(sendPhotoAction) forControlEvents:UIControlEventTouchUpInside]; 256 | _sendBtn.enabled = NO; 257 | } 258 | return _sendBtn; 259 | } 260 | -(TLCountLabel *)countLabel{ 261 | if (!_countLabel) { 262 | _countLabel = [[TLCountLabel alloc] init]; 263 | _countLabel.backgroundColor = UIColorFromRGB(0x007AFF); 264 | _countLabel.textColor = UIColorFromRGB(0xffffff); 265 | _countLabel.font = [UIFont systemFontOfSize:15]; 266 | _countLabel.hidden = YES; 267 | } 268 | return _countLabel; 269 | } 270 | -(UICollectionView *)photoCollectionView{ 271 | if (!_photoCollectionView) { 272 | _photoCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT) collectionViewLayout:self.colletionLayout]; 273 | _photoCollectionView.delegate = self; 274 | _photoCollectionView.dataSource = self; 275 | _photoCollectionView.showsVerticalScrollIndicator = NO; 276 | _photoCollectionView.scrollsToTop = YES; 277 | _photoCollectionView.alwaysBounceVertical = YES; 278 | [_photoCollectionView registerClass:[TLPhotoThumbCell class] forCellWithReuseIdentifier:@"cell"]; 279 | _photoCollectionView.backgroundColor = UIColorFromRGB(0xFFFAFAFA); 280 | } 281 | return _photoCollectionView; 282 | } 283 | -(UICollectionViewFlowLayout *)colletionLayout{ 284 | if (!_colletionLayout) { 285 | _colletionLayout = [[UICollectionViewFlowLayout alloc]init]; 286 | } 287 | return _colletionLayout; 288 | } 289 | - (void)didReceiveMemoryWarning { 290 | [super didReceiveMemoryWarning]; 291 | // Dispose of any resources that can be recreated. 292 | } 293 | @end 294 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLPhotoPreviewViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoPreviewViewController.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/25. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TLPhotoBrowser.h" 12 | 13 | @protocol TLPhotoPreviewDelegate 14 | -(void)selectedPhoto:(PHAsset *)photo; 15 | -(void)removePhoto:(PHAsset *)photo; 16 | -(void)sendPhotos; 17 | @end 18 | 19 | @interface TLPhotoPreviewViewController : UIViewController 20 | @property(nonatomic,assign)id delegate; 21 | -(instancetype)initWithSelectedAsset:(PHAsset *)selectedAsset assets:(NSArray *)asstes; 22 | @end 23 | 24 | 25 | @interface TLCountLabel : UILabel 26 | 27 | @end 28 | 29 | 30 | @interface TLPhotoPreviewCell : UICollectionViewCell 31 | @property(nonatomic,strong)TLImageScrollView *imgScrollView; 32 | @property(nonatomic,strong)PHAsset *asset; 33 | @end 34 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Controller/TLPhotoPreviewViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoPreviewViewController.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/25. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLPhotoPreviewViewController.h" 10 | #import "TLProjectMacro.h" 11 | #import "PHAsset+Extend.h" 12 | #import "TLButton.h" 13 | 14 | @interface TLPhotoPreviewViewController () 15 | 19 | @property(nonatomic,strong)UICollectionView *collectionView; 20 | @property(nonatomic,strong)UICollectionViewFlowLayout *colletionLayout; 21 | @property(nonatomic,strong)UIVisualEffectView *bottomView; 22 | @property(nonatomic,strong)UIVisualEffectView *navView; 23 | @property(nonatomic,strong)TLButton *sendBtn; 24 | @property(nonatomic,strong)TLCountLabel *countLabel; 25 | @property(nonatomic,strong)TLButton *selectBtn; 26 | @property(nonatomic,strong)TLButton *sendOriginalImgBtn; 27 | @property(nonatomic,strong)TLButton *backBtn; 28 | @property(nonatomic,strong)PHAsset *selectedAsset; 29 | @property(nonatomic,strong)NSArray *assets; 30 | @end 31 | 32 | @implementation TLPhotoPreviewViewController 33 | { 34 | BOOL _showNav; 35 | NSInteger _currentIndex; 36 | } 37 | -(void)viewWillAppear:(BOOL)animated{ 38 | [super viewWillAppear:animated]; 39 | [self.navigationController setNavigationBarHidden:YES animated:YES]; 40 | } 41 | -(void)viewWillDisappear:(BOOL)animated{ 42 | [super viewWillDisappear:animated]; 43 | [self.navigationController setNavigationBarHidden:NO animated:YES]; 44 | } 45 | -(instancetype)initWithSelectedAsset:(PHAsset *)selectedAsset assets:(NSArray *)asstes{ 46 | if (self = [super init]) { 47 | if (selectedAsset) { 48 | self.selectedAsset = selectedAsset; 49 | _currentIndex = [self.assets indexOfObject:selectedAsset]; 50 | self.selectBtn.selected = selectedAsset.selected; 51 | self.sendOriginalImgBtn.selected = selectedAsset.isOriginal; 52 | } 53 | self.assets = asstes; 54 | } 55 | return self; 56 | } 57 | - (void)viewDidLoad { 58 | [super viewDidLoad]; 59 | 60 | [self.view addSubview:self.collectionView]; 61 | 62 | self.view.backgroundColor = [UIColor blackColor]; 63 | 64 | self.collectionView.contentOffset = CGPointMake(_currentIndex * self.collectionView.bounds.size.width, 0); 65 | 66 | [self.view addSubview:self.navView]; 67 | [self.navView mas_makeConstraints:^(MASConstraintMaker *make) { 68 | make.top.left.right.equalTo(self.view); 69 | make.height.mas_offset(@49); 70 | }]; 71 | 72 | [self.navView addSubview:self.backBtn]; 73 | [self.backBtn mas_makeConstraints:^(MASConstraintMaker *make) { 74 | make.left.equalTo(self.navView.mas_left).offset(10); 75 | make.centerY.equalTo(self.navView.mas_centerY).offset(0); 76 | }]; 77 | 78 | [self.navView addSubview:self.selectBtn]; 79 | [self.selectBtn mas_makeConstraints:^(MASConstraintMaker *make) { 80 | make.right.equalTo(self.navView.mas_right).offset(-10); 81 | make.centerY.equalTo(self.navView.mas_centerY).offset(0); 82 | }]; 83 | 84 | [self.view addSubview:self.bottomView]; 85 | [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) { 86 | make.bottom.left.right.equalTo(self.view); 87 | make.height.mas_offset(@44); 88 | }]; 89 | 90 | [self.bottomView addSubview:self.sendOriginalImgBtn]; 91 | [self.sendOriginalImgBtn mas_makeConstraints:^(MASConstraintMaker *make) { 92 | make.left.equalTo(self.bottomView.mas_left).offset(10); 93 | make.centerY.equalTo(self.bottomView.mas_centerY).offset(0); 94 | }]; 95 | 96 | [self.bottomView addSubview:self.sendBtn]; 97 | [self.sendBtn mas_makeConstraints:^(MASConstraintMaker *make) { 98 | make.right.equalTo(self.bottomView.mas_right).offset(-10); 99 | make.centerY.equalTo(self.bottomView.mas_centerY).offset(0); 100 | }]; 101 | } 102 | -(void)backAction{ 103 | [self.navigationController popViewControllerAnimated:YES]; 104 | } 105 | -(void)sendPhotoAction:(UIButton *)sender{ 106 | if ([self.delegate respondsToSelector:@selector(sendPhotos)]) { 107 | [self.delegate sendPhotos]; 108 | } 109 | } 110 | -(void)selectAction:(UIButton *)sender{ 111 | PHAsset *currentAsset = self.assets[_currentIndex]; 112 | currentAsset.selected = !currentAsset.selected; 113 | sender.selected = currentAsset.selected; 114 | if (currentAsset.selected) { 115 | [self.delegate selectedPhoto:currentAsset]; 116 | }else{ 117 | [self.delegate removePhoto:currentAsset]; 118 | } 119 | } 120 | -(void)sendOriginalImgAction:(UIButton *)sender{ 121 | PHAsset *currentAsset = self.assets[_currentIndex]; 122 | currentAsset.isOriginal = !currentAsset.isOriginal; 123 | sender.selected = currentAsset.isOriginal; 124 | } 125 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 126 | TLPhotoPreviewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; 127 | cell.imgScrollView.actionDelegate = self; 128 | cell.asset = self.assets[indexPath.row]; 129 | return cell; 130 | } 131 | -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 132 | return self.assets.count; 133 | } 134 | #pragma mark - UICollectionViewDelegateFlowLayout 135 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{ 136 | return CGSizeMake(self.view.bounds.size.width + 20, self.view.bounds.size.height); 137 | } 138 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 139 | CGFloat offsetX = scrollView.contentOffset.x; 140 | CGFloat itemWidth = self.collectionView.frame.size.width; 141 | if (offsetX >= 0){ 142 | NSInteger index = offsetX / itemWidth; 143 | _currentIndex = index; 144 | PHAsset *currentAsset = self.assets[_currentIndex]; 145 | self.sendOriginalImgBtn.selected = currentAsset.isOriginal; 146 | self.selectBtn.selected = currentAsset.selected; 147 | } 148 | } 149 | -(void)imageScrollViewTap:(UITapGestureRecognizer *)sender{ 150 | [self.navView mas_updateConstraints:^(MASConstraintMaker *make) { 151 | make.top.equalTo(self.view.mas_top).offset(_showNav ? 0 : -69); 152 | }]; 153 | 154 | [self.bottomView mas_updateConstraints:^(MASConstraintMaker *make) { 155 | make.bottom.equalTo(self.view.mas_bottom).offset(_showNav ? 0 : 44); 156 | }]; 157 | 158 | [UIView animateWithDuration:0.25 animations:^{ 159 | [self.view layoutIfNeeded]; 160 | } completion:^(BOOL finished) { 161 | if (finished) { 162 | _showNav = !_showNav; 163 | } 164 | }]; 165 | } 166 | -(UICollectionView *)collectionView{ 167 | if (!_collectionView) { 168 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(- 10, 0, self.view.bounds.size.width + 20, self.view.bounds.size.height + 1) collectionViewLayout:self.colletionLayout]; 169 | _collectionView.backgroundColor = [UIColor blackColor]; 170 | _collectionView.delegate = self; 171 | _collectionView.dataSource = self; 172 | _collectionView.pagingEnabled = YES; 173 | _collectionView.showsHorizontalScrollIndicator = YES; 174 | [_collectionView registerClass:[TLPhotoPreviewCell class] forCellWithReuseIdentifier:@"cell"]; 175 | self.colletionLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 176 | } 177 | return _collectionView; 178 | } 179 | -(UICollectionViewFlowLayout *)colletionLayout{ 180 | if (!_colletionLayout) { 181 | _colletionLayout = [[UICollectionViewFlowLayout alloc] init]; 182 | _colletionLayout.minimumInteritemSpacing = 0; 183 | _colletionLayout.minimumLineSpacing = 0; 184 | _colletionLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 185 | } 186 | return _colletionLayout; 187 | } 188 | -(UIVisualEffectView *)navView{ 189 | if (!_navView) { 190 | _navView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; 191 | } 192 | return _navView; 193 | } 194 | -(UIVisualEffectView *)bottomView{ 195 | if (!_bottomView) { 196 | _bottomView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]]; 197 | } 198 | return _bottomView; 199 | } 200 | -(TLButton *)sendBtn{ 201 | if (!_sendBtn) { 202 | _sendBtn = [TLButton buttonWithType:UIButtonTypeCustom]; 203 | [_sendBtn setTitleColor:UIColorFromRGB(0x007AFF) forState:UIControlStateNormal]; 204 | [_sendBtn setTitleColor:UIColorFromRGB(0xa3cdff) forState:UIControlStateDisabled]; 205 | [_sendBtn setTitle:@"发送" forState:UIControlStateNormal]; 206 | _sendBtn.titleLabel.font = [UIFont systemFontOfSize:15]; 207 | [_sendBtn addTarget:self action:@selector(sendPhotoAction:) forControlEvents:UIControlEventTouchUpInside]; 208 | } 209 | return _sendBtn; 210 | } 211 | -(TLButton *)backBtn{ 212 | if (!_backBtn) { 213 | _backBtn = [TLButton buttonWithType:UIButtonTypeCustom]; 214 | [_backBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal]; 215 | [_backBtn addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside]; 216 | } 217 | return _backBtn; 218 | } 219 | -(TLButton *)selectBtn{ 220 | if (!_selectBtn) { 221 | _selectBtn = [TLButton buttonWithType:UIButtonTypeCustom]; 222 | [_selectBtn setImage:[UIImage imageNamed:@"photo_preview_unselected"] forState:UIControlStateNormal]; 223 | [_selectBtn setImage:[UIImage imageNamed:@"photo_preview_selected"] forState:UIControlStateSelected]; 224 | [_selectBtn addTarget:self action:@selector(selectAction:) forControlEvents:UIControlEventTouchUpInside]; 225 | } 226 | return _selectBtn; 227 | } 228 | -(TLCountLabel *)countLabel{ 229 | if (!_countLabel) { 230 | _countLabel = [[TLCountLabel alloc] init]; 231 | _countLabel.backgroundColor = UIColorFromRGB(0x007AFF); 232 | _countLabel.textColor = UIColorFromRGB(0xffffff); 233 | _countLabel.font = [UIFont systemFontOfSize:15]; 234 | _countLabel.hidden = YES; 235 | } 236 | return _countLabel; 237 | } 238 | -(TLButton *)sendOriginalImgBtn{ 239 | if (!_sendOriginalImgBtn) { 240 | _sendOriginalImgBtn = [TLButton buttonWithType:UIButtonTypeCustom]; 241 | [_sendOriginalImgBtn setImage:[UIImage imageNamed:@"unselected_full"] forState:UIControlStateNormal]; 242 | [_sendOriginalImgBtn setImage:[UIImage imageNamed:@"selected_full"] forState:UIControlStateSelected]; 243 | [_sendOriginalImgBtn setTitle:@"原图" forState:UIControlStateNormal]; 244 | [_sendOriginalImgBtn setTitleColor:UIColorFromRGB(0xffffff) forState:UIControlStateNormal]; 245 | [_sendOriginalImgBtn setTitleColor:UIColorFromRGB(0xcccccc) forState:UIControlStateSelected]; 246 | _sendOriginalImgBtn.titleLabel.font = [UIFont systemFontOfSize:15]; 247 | _sendOriginalImgBtn.imageEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 0); 248 | [_sendOriginalImgBtn addTarget:self action:@selector(sendOriginalImgAction:) forControlEvents:UIControlEventTouchUpInside]; 249 | } 250 | return _sendOriginalImgBtn; 251 | } 252 | -(BOOL)prefersStatusBarHidden{ 253 | return YES; 254 | } 255 | - (void)didReceiveMemoryWarning { 256 | [super didReceiveMemoryWarning]; 257 | // Dispose of any resources that can be recreated. 258 | } 259 | @end 260 | 261 | 262 | 263 | 264 | @implementation TLCountLabel 265 | -(instancetype)init{ 266 | if (self = [super init]) { 267 | self.clipsToBounds = YES; 268 | self.textAlignment = NSTextAlignmentCenter; 269 | self.backgroundColor = UIColorFromRGB(0xff514e); 270 | self.font = [UIFont systemFontOfSize:15]; 271 | self.textColor = UIColorFromRGB(0xffffff); 272 | } 273 | return self; 274 | } 275 | -(CGSize)intrinsicContentSize{ 276 | CGSize size = [super intrinsicContentSize]; 277 | return CGSizeMake(MAX(size.width + 8, size.height + 4), size.height + 4); 278 | } 279 | -(void)layoutSubviews{ 280 | [super layoutSubviews]; 281 | self.layer.cornerRadius = self.frame.size.height / 2.0; 282 | } 283 | @end 284 | 285 | 286 | 287 | 288 | 289 | @implementation TLPhotoPreviewCell 290 | -(instancetype)initWithFrame:(CGRect)frame{ 291 | if (self = [super initWithFrame:frame]) { 292 | if (!self.imgScrollView) { 293 | self.imgScrollView = [[TLImageScrollView alloc] initWithFrame:self.bounds]; 294 | [self addSubview:self.imgScrollView]; 295 | } 296 | } 297 | return self; 298 | } 299 | -(void)setAsset:(PHAsset *)asset{ 300 | [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:CGSizeMake(asset.pixelWidth, asset.pixelHeight) contentMode:PHImageContentModeDefault options:[[PHImageRequestOptions alloc] init] resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { 301 | self.imgScrollView.img = result; 302 | }]; 303 | } 304 | @end 305 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Networking/APIDebug.h: -------------------------------------------------------------------------------- 1 | // 2 | // APIDebug.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/24. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLProjectMacro.h" 10 | 11 | @interface APIDebug : NSObject 12 | +(void)configWithVC:(UIViewController *)vc; 13 | +(void)sendMsgWithContent:(NSString *)content 14 | objectName:(NSString *)objectName 15 | success:(void(^)())success 16 | failure:(void(^)(NSError *error))failure; 17 | @end 18 | 19 | 20 | @interface ActionSheet : UIActionSheet 21 | -(instancetype)initWithTitle:(NSString *)title cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle action:(void(^)(NSInteger))action otherButtonTitles:(NSArray *)otherButtonTitles; 22 | @end -------------------------------------------------------------------------------- /TLMessageView/Chat/Networking/APIDebug.m: -------------------------------------------------------------------------------- 1 | // 2 | // APIDebug.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/24. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "APIDebug.h" 10 | #import 11 | #import "TLProjectMacro.h" 12 | 13 | static NSString *Host = @"https://api.cn.ronghub.com"; 14 | static NSString *Url = @"/message/private/publish.json"; 15 | static NSString *AppKey = @"y745wfm844a4v"; 16 | static NSString *Appsecret = @"eIPrvtxgHw"; 17 | 18 | static NSString *const kAFCharactersGeneralDelimitersToEncode =@":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4 19 | static NSString *const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; 20 | 21 | 22 | @interface APIDebug () 23 | 24 | @end 25 | 26 | @implementation APIDebug 27 | +(void)configWithVC:(UIViewController *)vc{ 28 | UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"APIDebug" style:UIBarButtonItemStylePlain target:self action:@selector(debugAction)]; 29 | item.tintColor = [UIColor grayColor]; 30 | vc.navigationItem.rightBarButtonItem = item; 31 | } 32 | +(void)debugAction{ 33 | ActionSheet *sheet = [[ActionSheet alloc] initWithTitle:@"模拟融云服务器向当前用户发送消息" cancelButtonTitle:@"取消" destructiveButtonTitle:nil action:^(NSInteger x) { 34 | NSString *content = nil; 35 | NSString *objName = nil; 36 | 37 | NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; 38 | [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]]; 39 | switch (x) { 40 | case 1: 41 | { 42 | content = @"{\"content\":\"hello\",\"extra\":\"helloExtra\"}"; 43 | objName = @"RC:TxtMsg"; 44 | } 45 | break; 46 | case 2: 47 | { 48 | 49 | content = @"{\"content\":\"iVBORw0KGgoAAAANSUhEUgAAAA8AAAARCAIAAACNaGH2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACg0lEQVQokQXBS2+UZRQA4HPe23cfOkNn2grShmmpBMMCMRoSFv4C1+re6MKFf8XEJf+jbtiTuNGFaQSkpWl1mDKl813fy3mPz4NnS1+NegdUaNlyAjQkmHrwgJKl1rbxyhQsOyExOpGNfDD5WOU9aIUkdZEKRJmMpFTnLrABkfRsK3KAoP47e2ttaPp1AJD1ek0sAtVD75v60eMvt3fujWJo2DeqaNypevbrL9F7RAGI3jnmyIAcWTC+ev3XTz/+3G1uSeuEdKt1UPP5/OTNCccoAbbu3s1kyjKqTJpI7QDnXXsr1JTNEtcebu6pL55+9eb1MwaAGB9+9vjzJwcmmbr2fVpOUhhpgA/QZeR7qWRktfvgweZs8/Ld0iP++fuLadr6fLe9XIhMOVLGDtOD+e7OtknyAQiXFP54fvTb0RFRZOBSZV4gSJR+sCyctYcP73/97Xf11ZpiJSLT/SdPhVTMLIWsqUffeTu4CMBhNKlWy0VuaTr7aDzWSsXBpOV8f358fGwA8py/+eH70WRPKefWC13eJiYSqTB+659KFCzl4Lf3trUQlqFrpKmmG0YZsZFN9ot03IY6SMx9cvVJEB+Qg6Zbn97TKBA4xPD29GWtCkE2QiJ9O8tvCxBWgbmwQusSIx3ODiAzMTIgtv92uq9tUiq0jZAWqABlADGAWF+8HISIIEZlkRZJVpjr99dqqDWI7O+FDCKhwBBrCt0dgxfXFzfTaq3y66tL3a+suVHKVbqxf746DX5jZzY2SB71DXIdEC6pEWw49p5UmuY5vDqjSXK5UOOPdQxsSuNarzOJ0rtOSDYxdizkcFI774f+ZgXVbHJHqUSnJTKRziDKDCmcw//w3WEJ8Go/SwAAAABJRU5ErkJggg==\",\"imageUri\":\"http://7i7gc6.com1.z0.glb.clouddn.com/emoji.png\",\"extra\":\"helloExtra\"}"; 50 | 51 | objName = @"RC:ImgMsg"; 52 | } 53 | break; 54 | case 3: 55 | { 56 | content = @"{\"content\":\"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\",\"duration\":2,\"extra\":\"helloExtra\"}"; 57 | 58 | 59 | objName = @"RC:VcMsg"; 60 | 61 | } 62 | break; 63 | case 4: 64 | { 65 | content = @"{\"content\":\"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\",\"latitude\":40.032149,\"longitude\":116.417613,\"poi\":\"北京经济技术研修学院(北苑路)\",\"extra\":\"helloExtra\"}"; 66 | 67 | objName = @"RC:LBSMsg"; 68 | 69 | } 70 | break; 71 | default: 72 | break; 73 | } 74 | content = [content stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; 75 | 76 | [APIDebug sendMsgWithContent:content objectName:objName success:^{ 77 | 78 | } failure:^(NSError *error) { 79 | 80 | }]; 81 | 82 | } otherButtonTitles:@[@"发送文字消息",@"发送图片消息",@"发送语音消息",@"发送地理位置消息"]]; 83 | [sheet showInView:kKeyWindow]; 84 | } 85 | +(void)sendMsgWithContent:(NSString *)content 86 | objectName:(NSString *)objectName 87 | success:(void (^)())success 88 | failure:(void (^)(NSError *))failure{ 89 | NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",Host,Url]]; 90 | 91 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 92 | [request setHTTPMethod:@"POST"]; 93 | [request setTimeoutInterval:10]; 94 | 95 | NSString *random = @(1000000000 + (arc4random() % (1000000000 - 1))).stringValue; 96 | 97 | NSString *appKey = AppKey; 98 | 99 | NSString *secret = Appsecret; 100 | 101 | NSString *time = [@([[NSDate date] timeIntervalSince1970]).stringValue componentsSeparatedByString:@"."][0]; 102 | 103 | NSString *signature = [self sha1:[NSString stringWithFormat:@"%@%@%@",secret,random,time]]; 104 | 105 | NSDictionary *header = @{@"App-Key":appKey,@"Timestamp":time,@"Nonce":random,@"Signature":signature,@"Content-Type":@"application/x-www-form-urlencoded"}; 106 | [request setAllHTTPHeaderFields:header]; 107 | 108 | NSString *params = [NSString stringWithFormat:@"content=%@&fromUserId=%@&toUserId=%@&objectName=%@",content,@"222",@"111",objectName]; 109 | 110 | NSData *bodyData = [params dataUsingEncoding:NSUTF8StringEncoding]; 111 | [request setHTTPBody:bodyData]; 112 | 113 | NSURLSessionDataTask *task = [ [NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 114 | if (error) { 115 | failure(error); 116 | }else{ 117 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; 118 | NSLog(@"%@",json); 119 | } 120 | }]; 121 | [task resume]; 122 | 123 | } 124 | +(NSString*)dataTOjsonString:(id)object 125 | { 126 | NSString *jsonString = nil; 127 | NSError *error; 128 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object 129 | options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string 130 | error:&error]; 131 | if (! jsonData) { 132 | NSLog(@"Got an error: %@", error); 133 | } else { 134 | jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 135 | } 136 | return jsonString; 137 | } 138 | + (NSString *) sha1:(NSString *)input 139 | { 140 | const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding]; 141 | NSData *data = [NSData dataWithBytes:cstr length:input.length]; 142 | 143 | uint8_t digest[CC_SHA1_DIGEST_LENGTH]; 144 | 145 | CC_SHA1(data.bytes, (unsigned int)data.length, digest); 146 | 147 | NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; 148 | 149 | for(int i=0; i 7 | 8 | @interface LPlaceholderTextView : UITextView 9 | { 10 | UILabel *_placeholderLabel; 11 | } 12 | 13 | 14 | @property (strong, nonatomic) NSString *placeholderText; 15 | @property (strong, nonatomic) UIColor *placeholderColor; 16 | @property (assign, nonatomic) CGFloat maxTextViewHeight; 17 | @end -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/LPlaceholderTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Luka Gabrić. 3 | // Copyright (c) 2013 Luka Gabrić. All rights reserved. 4 | // 5 | 6 | 7 | #import "LPlaceholderTextView.h" 8 | 9 | 10 | @implementation LPlaceholderTextView 11 | 12 | 13 | #pragma mark - init & dealloc 14 | 15 | 16 | - (id)initWithCoder:(NSCoder *)aDecoder 17 | { 18 | self = [super initWithCoder:aDecoder]; 19 | if (self) 20 | { 21 | [self initialize]; 22 | } 23 | 24 | return self; 25 | } 26 | 27 | 28 | - (id)initWithFrame:(CGRect)frame 29 | { 30 | self = [super initWithFrame:frame]; 31 | if (self) 32 | { 33 | [self initialize]; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | 40 | - (id)init 41 | { 42 | self = [super init]; 43 | if (self) 44 | { 45 | [self initialize]; 46 | } 47 | 48 | return self; 49 | } 50 | 51 | 52 | - (void)initialize 53 | { 54 | _placeholderColor = [UIColor lightGrayColor]; 55 | [self layoutGUI]; 56 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:self]; 57 | } 58 | 59 | 60 | - (void)dealloc 61 | { 62 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 63 | } 64 | 65 | 66 | #pragma mark - Notification center 67 | 68 | 69 | - (void)textChanged:(NSNotification *)notification 70 | { 71 | if (notification.object == self) 72 | [self layoutGUI]; 73 | } 74 | 75 | 76 | #pragma mark - layoutGUI 77 | 78 | 79 | - (void)layoutGUI 80 | { 81 | _placeholderLabel.alpha = [self.text length] > 0 || [_placeholderText length] == 0 ? 0 : 1; 82 | } 83 | 84 | 85 | #pragma mark - Setters 86 | 87 | 88 | - (void)setText:(NSString *)text 89 | { 90 | [super setText:text]; 91 | [self layoutGUI]; 92 | } 93 | - (void)setAttributedText:(NSAttributedString *)attributedText 94 | { 95 | [super setAttributedText:attributedText]; 96 | [self layoutGUI]; 97 | } 98 | 99 | - (void)setPlaceholderText:(NSString*)placeholderText 100 | { 101 | _placeholderText = placeholderText; 102 | [self setNeedsDisplay]; 103 | } 104 | 105 | 106 | - (void)setPlaceholderColor:(UIColor*)color 107 | { 108 | _placeholderColor = color; 109 | [self setNeedsDisplay]; 110 | } 111 | 112 | 113 | #pragma mark - drawRect 114 | 115 | 116 | - (void)drawRect:(CGRect)rect 117 | { 118 | if ([_placeholderText length] > 0) 119 | { 120 | if (!_placeholderLabel) 121 | { 122 | _placeholderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, self.bounds.size.width - 16, 0)]; 123 | _placeholderLabel.lineBreakMode = NSLineBreakByWordWrapping; 124 | _placeholderLabel.numberOfLines = 0; 125 | _placeholderLabel.font = self.font; 126 | _placeholderLabel.backgroundColor = [UIColor clearColor]; 127 | _placeholderLabel.alpha = 0; 128 | [self addSubview:_placeholderLabel]; 129 | } 130 | 131 | _placeholderLabel.text = _placeholderText; 132 | _placeholderLabel.textColor = _placeholderColor; 133 | [_placeholderLabel sizeToFit]; 134 | [self sendSubviewToBack:_placeholderLabel]; 135 | } 136 | 137 | [self layoutGUI]; 138 | 139 | [super drawRect:rect]; 140 | } 141 | -(CGSize)contentSize{ 142 | CGSize size = [super contentSize]; 143 | CGSize maxSize = CGSizeMake(self.frame.size.width, MIN(self.maxTextViewHeight, size.height)); 144 | return maxSize; 145 | } 146 | 147 | #pragma mark - 148 | 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/NSDate+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Utils.h 3 | // BloodSugar 4 | // 5 | // Created by PeterPan on 13-12-27. 6 | // Copyright (c) 2013年 shake. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface NSDate (Utils) 13 | 14 | + (NSDate *)dateWithYear:(NSInteger)year 15 | month:(NSInteger)month 16 | day:(NSInteger)day 17 | hour:(NSInteger)hour 18 | minute:(NSInteger)minute 19 | second:(NSInteger)second; 20 | 21 | + (NSInteger)daysOffsetBetweenStartDate:(NSDate *)startDate endDate:(NSDate *)endDate; 22 | 23 | + (NSDate *)dateWithHour:(int)hour 24 | minute:(int)minute; 25 | 26 | #pragma mark - Getter 27 | - (NSInteger)year; 28 | - (NSInteger)month; 29 | - (NSInteger)day; 30 | - (NSInteger)hour; 31 | - (NSInteger)minute; 32 | - (NSInteger)second; 33 | - (NSString *)weekday; 34 | 35 | 36 | #pragma mark - Time string 37 | - (NSString *)timeHourMinute; 38 | - (NSString *)timeHourMinuteWithPrefix; 39 | - (NSString *)timeHourMinuteWithSuffix; 40 | - (NSString *)timeHourMinuteWithPrefix:(BOOL)enablePrefix suffix:(BOOL)enableSuffix; 41 | 42 | #pragma mark - Date String 43 | - (NSString *)stringTime; 44 | - (NSString *)stringMonthDay; 45 | - (NSString *)stringYearMonthDay; 46 | - (NSString *)stringYearMonthDayHourMinuteSecond; 47 | + (NSString *)stringYearMonthDayWithDate:(NSDate *)date; //date为空时返回的是当前年月日 48 | + (NSString *)stringLoacalDate; 49 | 50 | #pragma mark - Date formate 51 | + (NSString *)dateFormatString; 52 | + (NSString *)timeFormatString; 53 | + (NSString *)timestampFormatString; 54 | + (NSString *)timestampFormatStringSubSeconds; 55 | 56 | #pragma mark - Date adjust 57 | - (NSDate *) dateByAddingDays: (NSInteger) dDays; 58 | - (NSDate *) dateBySubtractingDays: (NSInteger) dDays; 59 | 60 | #pragma mark - Relative dates from the date 61 | + (NSDate *) dateTomorrow; 62 | + (NSDate *) dateYesterday; 63 | + (NSDate *) dateWithDaysFromNow: (NSInteger) days; 64 | + (NSDate *) dateWithDaysBeforeNow: (NSInteger) days; 65 | + (NSDate *) dateWithHoursFromNow: (NSInteger) dHours; 66 | + (NSDate *) dateWithHoursBeforeNow: (NSInteger) dHours; 67 | + (NSDate *) dateWithMinutesFromNow: (NSInteger) dMinutes; 68 | + (NSDate *) dateWithMinutesBeforeNow: (NSInteger) dMinutes; 69 | + (NSDate *) dateStandardFormatTimeZeroWithDate: (NSDate *) aDate; //标准格式的零点日期 70 | - (NSInteger) daysBetweenCurrentDateAndDate; //负数为过去,正数为未来 71 | 72 | #pragma mark - Date compare 73 | - (BOOL)isEqualToDateIgnoringTime: (NSDate *) aDate; 74 | - (NSString *)stringYearMonthDayCompareToday; //返回“今天”,“明天”,“昨天”,或年月日 75 | 76 | #pragma mark - Date and string convert 77 | + (NSDate *)dateFromString:(NSString *)string; 78 | + (NSDate *)dateFromString:(NSString *)string withFormat:(NSString *)format; 79 | - (NSString *)string; 80 | - (NSString *)stringCutSeconds; 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/NSDate+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Utils.m 3 | // BloodSugar 4 | // 5 | // Created by PeterPan on 13-12-27. 6 | // Copyright (c) 2013年 shake. All rights reserved. 7 | // 8 | 9 | #import "NSDate+Utils.h" 10 | 11 | #define DATE_COMPONENTS (NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay | kCFCalendarUnitWeekOfMonth | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitWeekday | NSCalendarUnitWeekdayOrdinal) 12 | #define CURRENT_CALENDAR [NSCalendar currentCalendar] 13 | 14 | #define D_MINUTE 60 15 | #define D_HOUR 3600 16 | #define D_DAY 86400 17 | #define D_WEEK 604800 18 | #define D_YEAR 31556926 19 | 20 | @implementation NSDate (Utils) 21 | 22 | + (NSDate *)dateWithYear:(NSInteger)year 23 | month:(NSInteger)month 24 | day:(NSInteger)day 25 | hour:(NSInteger)hour 26 | minute:(NSInteger)minute 27 | second:(NSInteger)second{ 28 | 29 | NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; 30 | 31 | NSTimeZone *systemTimeZone = [NSTimeZone systemTimeZone]; 32 | 33 | NSDateComponents *dateComps = [[NSDateComponents alloc] init]; 34 | [dateComps setCalendar:gregorian]; 35 | [dateComps setYear:year]; 36 | [dateComps setMonth:month]; 37 | [dateComps setDay:day]; 38 | [dateComps setTimeZone:systemTimeZone]; 39 | [dateComps setHour:hour]; 40 | [dateComps setMinute:minute]; 41 | [dateComps setSecond:second]; 42 | 43 | 44 | return [dateComps date]; 45 | } 46 | 47 | + (NSInteger)daysOffsetBetweenStartDate:(NSDate *)startDate endDate:(NSDate *)endDate 48 | { 49 | NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; 50 | unsigned int unitFlags = NSCalendarUnitDay; 51 | NSDateComponents *comps = [gregorian components:unitFlags fromDate:startDate toDate:endDate options:0]; 52 | NSInteger days = [comps day]; 53 | return days; 54 | } 55 | 56 | + (NSDate *)dateWithHour:(int)hour 57 | minute:(int)minute 58 | { 59 | NSDate *now = [NSDate date]; 60 | NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; 61 | NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now]; 62 | [components setHour:hour]; 63 | [components setMinute:minute]; 64 | NSDate *newDate = [calendar dateFromComponents:components]; 65 | return newDate; 66 | } 67 | 68 | #pragma mark - Data component 69 | - (NSInteger)year 70 | { 71 | NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:self]; 72 | return [dateComponents year]; 73 | } 74 | 75 | - (NSInteger)month 76 | { 77 | NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:self]; 78 | return [dateComponents month]; 79 | } 80 | 81 | - (NSInteger)day 82 | { 83 | NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:self]; 84 | return [dateComponents day]; 85 | } 86 | 87 | - (NSInteger)hour 88 | { 89 | NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:self]; 90 | return [dateComponents hour]; 91 | } 92 | 93 | - (NSInteger)minute 94 | { 95 | NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:self]; 96 | return [dateComponents minute]; 97 | } 98 | 99 | - (NSInteger)second 100 | { 101 | NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:self]; 102 | return [dateComponents second]; 103 | } 104 | 105 | - (NSString *)weekday 106 | { 107 | NSCalendar*calendar = [NSCalendar currentCalendar]; 108 | NSDateComponents*comps; 109 | 110 | NSDate *date = [NSDate date]; 111 | comps =[calendar components:(NSCalendarUnitWeekOfMonth | NSCalendarUnitWeekday |NSCalendarUnitWeekdayOrdinal) 112 | fromDate:date]; 113 | NSInteger weekday = [comps weekday]; // 星期几(注意,周日是“1”,周一是“2”。。。。) 114 | NSString *week = @""; 115 | switch (weekday) { 116 | case 1: 117 | week = @"星期日"; 118 | break; 119 | case 2: 120 | week = @"星期一"; 121 | break; 122 | case 3: 123 | week = @"星期二"; 124 | break; 125 | case 4: 126 | week = @"星期三"; 127 | break; 128 | case 5: 129 | week = @"星期四"; 130 | break; 131 | case 6: 132 | week = @"星期五"; 133 | break; 134 | case 7: 135 | week = @"星期六"; 136 | break; 137 | 138 | default: 139 | break; 140 | } 141 | 142 | return week; 143 | } 144 | 145 | 146 | 147 | 148 | #pragma mark - Time string 149 | - (NSString *)timeHourMinute 150 | { 151 | 152 | return [self timeHourMinuteWithPrefix:NO suffix:NO]; 153 | } 154 | 155 | - (NSString *)timeHourMinuteWithPrefix 156 | { 157 | return [self timeHourMinuteWithPrefix:YES suffix:NO]; 158 | } 159 | 160 | - (NSString *)timeHourMinuteWithSuffix 161 | { 162 | return [self timeHourMinuteWithPrefix:NO suffix:YES]; 163 | } 164 | 165 | - (NSString *)timeHourMinuteWithPrefix:(BOOL)enablePrefix suffix:(BOOL)enableSuffix 166 | { 167 | NSDateFormatter *formatter = [[NSDateFormatter alloc]init]; 168 | [formatter setDateFormat:@"HH:mm"]; 169 | NSString *timeStr = [formatter stringFromDate:self]; 170 | if (enablePrefix) { 171 | timeStr = [NSString stringWithFormat:@"%@%@",([self hour] > 12 ? @"下午" : @"上午"),timeStr]; 172 | } 173 | if (enableSuffix) { 174 | timeStr = [NSString stringWithFormat:@"%@%@",([self hour] > 12 ? @"pm" : @"am"),timeStr]; 175 | } 176 | return timeStr; 177 | } 178 | 179 | 180 | #pragma mark - Date String 181 | - (NSString *)stringTime 182 | { 183 | NSDateFormatter *formatter = [[NSDateFormatter alloc]init]; 184 | [formatter setDateFormat:@"HH:mm"]; 185 | NSString *str = [formatter stringFromDate:self]; 186 | return str; 187 | } 188 | 189 | - (NSString *)stringMonthDay 190 | { 191 | return [NSDate dateMonthDayWithDate:self]; 192 | } 193 | 194 | - (NSString *)stringYearMonthDay 195 | { 196 | return [NSDate stringYearMonthDayWithDate:self]; 197 | } 198 | 199 | - (NSString *)stringYearMonthDayHourMinuteSecond 200 | { 201 | NSDateFormatter *formatter = [[NSDateFormatter alloc]init]; 202 | [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 203 | NSString *str = [formatter stringFromDate:self]; 204 | return str; 205 | 206 | } 207 | 208 | - (NSString *)stringYearMonthDayCompareToday 209 | { 210 | NSString *str; 211 | NSInteger chaDay = [self daysBetweenCurrentDateAndDate]; 212 | if (chaDay == 0) { 213 | str = @"今天"; 214 | }else if (chaDay == 1){ 215 | str = @"明天"; 216 | }else if (chaDay == -1){ 217 | str = @"昨天"; 218 | }else{ 219 | str = [self stringYearMonthDay]; 220 | } 221 | 222 | return str; 223 | } 224 | 225 | + (NSString *)stringLoacalDate 226 | { 227 | NSDateFormatter *format = [[NSDateFormatter alloc] init]; 228 | [format setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; 229 | [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 230 | NSDate *date = [NSDate date]; 231 | NSTimeZone *zone = [NSTimeZone systemTimeZone]; 232 | NSInteger interval = [zone secondsFromGMTForDate: date]; 233 | NSDate *localeDate = [date dateByAddingTimeInterval: interval]; 234 | NSString *dateStr = [format stringFromDate:localeDate]; 235 | 236 | return dateStr; 237 | } 238 | 239 | + (NSString *)stringYearMonthDayWithDate:(NSDate *)date 240 | { 241 | if (date == nil) { 242 | date = [NSDate date]; 243 | } 244 | NSDateFormatter *formatter = [[NSDateFormatter alloc]init]; 245 | [formatter setDateFormat:@"yyyy-MM-dd"]; 246 | NSString *str = [formatter stringFromDate:date]; 247 | return str; 248 | } 249 | 250 | + (NSString *)dateMonthDayWithDate:(NSDate *)date 251 | { 252 | if (date == nil) { 253 | date = [NSDate date]; 254 | } 255 | 256 | NSDateFormatter *formatter = [[NSDateFormatter alloc]init]; 257 | [formatter setDateFormat:@"MM.dd"]; 258 | NSString *str = [formatter stringFromDate:date]; 259 | return str; 260 | } 261 | 262 | 263 | #pragma mark - Date formate 264 | 265 | + (NSString *)dateFormatString { 266 | return @"yyyy-MM-dd"; 267 | } 268 | 269 | + (NSString *)timeFormatString { 270 | return @"HH:mm:ss"; 271 | } 272 | 273 | + (NSString *)timestampFormatString { 274 | return @"yyyy-MM-dd HH:mm:ss"; 275 | } 276 | 277 | + (NSString *)timestampFormatStringSubSeconds 278 | { 279 | return @"yyyy-MM-dd HH:mm"; 280 | } 281 | 282 | #pragma mark - Date adjust 283 | - (NSDate *) dateByAddingDays: (NSInteger) dDays 284 | { 285 | NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + D_DAY * dDays; 286 | NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; 287 | return newDate; 288 | } 289 | 290 | - (NSDate *) dateBySubtractingDays: (NSInteger) dDays 291 | { 292 | return [self dateByAddingDays: (dDays * -1)]; 293 | } 294 | 295 | #pragma mark - Relative Dates 296 | + (NSDate *) dateWithDaysFromNow: (NSInteger) days 297 | { 298 | // Thanks, Jim Morrison 299 | return [[NSDate date] dateByAddingDays:days]; 300 | } 301 | 302 | + (NSDate *) dateWithDaysBeforeNow: (NSInteger) days 303 | { 304 | // Thanks, Jim Morrison 305 | return [[NSDate date] dateBySubtractingDays:days]; 306 | } 307 | 308 | + (NSDate *) dateTomorrow 309 | { 310 | return [NSDate dateWithDaysFromNow:1]; 311 | } 312 | 313 | + (NSDate *) dateYesterday 314 | { 315 | return [NSDate dateWithDaysBeforeNow:1]; 316 | } 317 | 318 | + (NSDate *) dateWithHoursFromNow: (NSInteger) dHours 319 | { 320 | NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_HOUR * dHours; 321 | NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; 322 | return newDate; 323 | } 324 | 325 | + (NSDate *) dateWithHoursBeforeNow: (NSInteger) dHours 326 | { 327 | NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_HOUR * dHours; 328 | NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; 329 | return newDate; 330 | } 331 | 332 | + (NSDate *) dateWithMinutesFromNow: (NSInteger) dMinutes 333 | { 334 | NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] + D_MINUTE * dMinutes; 335 | NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; 336 | return newDate; 337 | } 338 | 339 | + (NSDate *) dateWithMinutesBeforeNow: (NSInteger) dMinutes 340 | { 341 | NSTimeInterval aTimeInterval = [[NSDate date] timeIntervalSinceReferenceDate] - D_MINUTE * dMinutes; 342 | NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval]; 343 | return newDate; 344 | } 345 | 346 | 347 | - (BOOL) isEqualToDateIgnoringTime: (NSDate *) aDate 348 | { 349 | NSDateComponents *components1 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:self]; 350 | NSDateComponents *components2 = [CURRENT_CALENDAR components:DATE_COMPONENTS fromDate:aDate]; 351 | return ((components1.year == components2.year) && 352 | (components1.month == components2.month) && 353 | (components1.day == components2.day)); 354 | } 355 | 356 | 357 | + (NSDate *) dateStandardFormatTimeZeroWithDate: (NSDate *) aDate{ 358 | NSString *str = [[NSDate stringYearMonthDayWithDate:aDate]stringByAppendingString:@" 00:00:00"]; 359 | NSDate *date = [NSDate dateFromString:str]; 360 | return date; 361 | } 362 | 363 | - (NSInteger) daysBetweenCurrentDateAndDate 364 | { 365 | //只取年月日比较 366 | NSDate *dateSelf = [NSDate dateStandardFormatTimeZeroWithDate:self]; 367 | NSTimeInterval timeInterval = [dateSelf timeIntervalSince1970]; 368 | NSDate *dateNow = [NSDate dateStandardFormatTimeZeroWithDate:nil]; 369 | NSTimeInterval timeIntervalNow = [dateNow timeIntervalSince1970]; 370 | 371 | NSTimeInterval cha = timeInterval - timeIntervalNow; 372 | CGFloat chaDay = cha / 86400.0; 373 | NSInteger day = chaDay * 1; 374 | return day; 375 | } 376 | 377 | #pragma mark - Date and string convert 378 | + (NSDate *)dateFromString:(NSString *)string { 379 | return [NSDate dateFromString:string withFormat:[NSDate dbFormatString]]; 380 | } 381 | 382 | + (NSDate *)dateFromString:(NSString *)string withFormat:(NSString *)format { 383 | NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init]; 384 | [inputFormatter setDateFormat:format]; 385 | NSDate *date = [inputFormatter dateFromString:string]; 386 | return date; 387 | } 388 | 389 | - (NSString *)string { 390 | return [self stringWithFormat:[NSDate dbFormatString]]; 391 | } 392 | 393 | - (NSString *)stringCutSeconds 394 | { 395 | return [self stringWithFormat:[NSDate timestampFormatStringSubSeconds]]; 396 | } 397 | 398 | - (NSString *)stringWithFormat:(NSString *)format { 399 | NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init]; 400 | [outputFormatter setDateFormat:format]; 401 | NSString *timestamp_str = [outputFormatter stringFromDate:self]; 402 | return timestamp_str; 403 | } 404 | 405 | + (NSString *)dbFormatString { 406 | return [NSDate timestampFormatString]; 407 | } 408 | @end 409 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/PHAsset+Extend.h: -------------------------------------------------------------------------------- 1 | // 2 | // PHAsset+Extend.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 2016/11/20. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PHAsset (Extend) 12 | //选中状态 13 | @property(nonatomic,assign)BOOL selected; 14 | //原图 15 | @property(nonatomic,assign)BOOL isOriginal; 16 | @end 17 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/PHAsset+Extend.m: -------------------------------------------------------------------------------- 1 | // 2 | // PHAsset+Extend.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 2016/11/20. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "PHAsset+Extend.h" 10 | #import 11 | 12 | static char selectedKey; 13 | static char originalKey; 14 | 15 | @implementation PHAsset (Extend) 16 | -(void)setSelected:(BOOL)selected{ 17 | objc_setAssociatedObject(self, &selectedKey, @(selected), OBJC_ASSOCIATION_ASSIGN); 18 | } 19 | -(BOOL)selected{ 20 | return [objc_getAssociatedObject(self, &selectedKey) boolValue]; 21 | } 22 | -(void)setIsOriginal:(BOOL)isOriginal{ 23 | objc_setAssociatedObject(self, &originalKey, @(isOriginal), OBJC_ASSOCIATION_ASSIGN); 24 | } 25 | -(BOOL)isOriginal{ 26 | return [objc_getAssociatedObject(self, &originalKey) boolValue]; 27 | } 28 | @end 29 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/TLRCManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLRCManager.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/20. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLProjectMacro.h" 10 | 11 | @protocol TLRCManagerDelegate 12 | -(void)rcManagerReceiveMsg:(RCMessage *)msg; 13 | @end 14 | 15 | @interface TLRCManager : NSObject 16 | @property(nonatomic,assign)id delegate; 17 | +(TLRCManager *)shareManager; 18 | - (void)initEnv; 19 | - (void)connectWithToken:(NSString*)token; 20 | - (void)sendMessage:(RCMessage *)message 21 | successBlock:(void(^)(RCMessage *))successBlock 22 | failedBlock:(void(^)(RCMessage *))failedBlock; 23 | @end 24 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/TLRCManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLRCManager.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/20. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLRCManager.h" 10 | 11 | static NSString *RongCloundKey = @"y745wfm844a4v"; 12 | 13 | @interface TLRCManager () 14 | 15 | @end 16 | 17 | @implementation TLRCManager 18 | +(TLRCManager *)shareManager{ 19 | static dispatch_once_t onceToken; 20 | static TLRCManager *player; 21 | dispatch_once(&onceToken, ^{ 22 | player = [[TLRCManager alloc] init]; 23 | }); 24 | return player; 25 | } 26 | - (instancetype) init{ 27 | if (self = [super init]) { 28 | [[RCIMClient sharedRCIMClient] setReceiveMessageDelegate:self object:nil]; 29 | [[RCIMClient sharedRCIMClient] setRCConnectionStatusChangeDelegate:self]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void) initEnv{ 35 | [[RCIMClient sharedRCIMClient] initWithAppKey:RongCloundKey]; 36 | } 37 | 38 | - (void) disconnect{ 39 | [[RCIMClient sharedRCIMClient] logout]; 40 | } 41 | 42 | - (void) connectWithToken:(NSString*)token{ 43 | if (!token) return; 44 | [[RCIMClient sharedRCIMClient] connectWithToken:token success:^(NSString *userId) { 45 | NSLog(@"****************************************"); 46 | NSLog(@"Rong connectWithToken success"); 47 | NSLog(@"Rong uid is:%@", userId); 48 | NSLog(@"****************************************"); 49 | } error:^(RCConnectErrorCode status) { 50 | NSLog(@"💥Rong connectWithToken error:%tu", status); 51 | } tokenIncorrect:^{ 52 | NSLog(@"💥Rong connectWithToken incorrect"); 53 | }]; 54 | } 55 | - (void) sendMessage:(RCMessage *)message 56 | successBlock:(void(^)(RCMessage *))successBlock 57 | failedBlock:(void(^)(RCMessage *))failedBlock 58 | { 59 | [[RCIMClient sharedRCIMClient] sendMessage:message.conversationType targetId:message.targetId content:message.content pushContent:nil pushData:nil success:^(long messageId) { 60 | dispatch_async(dispatch_get_main_queue(), ^{ 61 | if (successBlock) { 62 | successBlock(message); 63 | } 64 | }); 65 | } error:^(RCErrorCode nErrorCode, long messageId) { 66 | dispatch_async(dispatch_get_main_queue(), ^{ 67 | if (failedBlock) { 68 | failedBlock(message); 69 | } 70 | }); 71 | }]; 72 | } 73 | - (void)onReceived:(RCMessage *)message 74 | left:(int)nLeft 75 | object:(id)object{ 76 | if (message.conversationType == ConversationType_PRIVATE) { 77 | if ([self.delegate respondsToSelector:@selector(rcManagerReceiveMsg:)]) { 78 | [self.delegate rcManagerReceiveMsg:message]; 79 | } 80 | } 81 | } 82 | 83 | - (void)onConnectionStatusChanged:(RCConnectionStatus)status 84 | { 85 | NSLog(@"Rong cloud status:%tu", status); 86 | } 87 | @end 88 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/TLRecordVoice.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLRecordVoice.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol TLRecorderVoiceDelegate 12 | -(void)recorderVoiceFailure; 13 | -(void)recorderVoiceSuccessWithVoiceData:(NSData *)voiceData duration:(long)duration; 14 | @end 15 | 16 | @interface TLRecordVoice : NSObject 17 | @property(nonatomic,assign)id delegate; 18 | -(instancetype)initWithDelegate:(id )delegate; 19 | -(void)startRecord; 20 | -(void)completeRecord; 21 | -(void)cancelRecord; 22 | @end 23 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/TLRecordVoice.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLRecordVoice.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLRecordVoice.h" 10 | #import 11 | #import "TLRecordVoiceHUD.h" 12 | 13 | @interface TLRecordVoice () 14 | @property(nonatomic,strong)AVAudioSession *audioSession; 15 | @property(nonatomic,strong)AVAudioRecorder *audioRecorder; 16 | @property(nonatomic,strong)NSTimer *timer; 17 | @end 18 | 19 | @implementation TLRecordVoice 20 | -(instancetype)initWithDelegate:(id)delegate{ 21 | if (self = [super init]) { 22 | self.delegate = delegate; 23 | } 24 | return self; 25 | } 26 | -(void)startRecord{ 27 | [self configSession]; 28 | [self configRecord]; 29 | [self.audioRecorder record]; 30 | 31 | [TLRecordVoiceHUD showRecording]; 32 | self.timer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(observerVoiceVolume) userInfo:nil repeats:YES]; 33 | } 34 | -(void)completeRecord{ 35 | if (self.audioRecorder.currentTime > 2) { 36 | [TLRecordVoiceHUD dismiss]; 37 | NSData *data = [[NSData alloc] initWithContentsOfURL:[self recordFilePath]]; 38 | if ([self.delegate respondsToSelector:@selector(recorderVoiceSuccessWithVoiceData:duration:)]) { 39 | [self.delegate recorderVoiceSuccessWithVoiceData:data duration:self.audioRecorder.currentTime]; 40 | } 41 | }else { 42 | [TLRecordVoiceHUD dismissWithRecordShort]; 43 | } 44 | [self endRecord]; 45 | } 46 | -(void)cancelRecord{ 47 | [self endRecord]; 48 | [TLRecordVoiceHUD dismiss]; 49 | } 50 | - (void)endRecord{ 51 | [self.audioRecorder deleteRecording]; 52 | [self.audioRecorder stop]; 53 | [self.timer invalidate]; 54 | } 55 | - (void)observerVoiceVolume{ 56 | [self.audioRecorder updateMeters]; 57 | CGFloat lowPassResults = pow(10, (0.05 * [self.audioRecorder peakPowerForChannel:0])); 58 | [TLRecordVoiceHUD updatePeakPower:lowPassResults]; 59 | } 60 | 61 | -(void)configSession{ 62 | self.audioSession = [AVAudioSession sharedInstance]; 63 | 64 | NSError *error; 65 | [self.audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; 66 | 67 | if (self.audioSession) { 68 | [self.audioSession setActive:YES error:nil]; 69 | } 70 | } 71 | -(void)configRecord{ 72 | NSDictionary *settings = @{AVFormatIDKey : @(kAudioFormatLinearPCM), 73 | AVSampleRateKey : @8000.00f, 74 | AVNumberOfChannelsKey : @1, 75 | AVLinearPCMBitDepthKey : @16, 76 | AVLinearPCMIsNonInterleaved : @NO, 77 | AVLinearPCMIsFloatKey : @NO, 78 | AVLinearPCMIsBigEndianKey : @NO}; 79 | 80 | NSURL *tmpUrl = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp"]]; 81 | 82 | NSError *error; 83 | self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:tmpUrl 84 | settings:settings 85 | error:&error]; 86 | if (error) NSLog(@"%@",error); 87 | 88 | self.audioRecorder.meteringEnabled = YES; 89 | self.audioRecorder.delegate = self; 90 | [self.audioRecorder prepareToRecord]; 91 | } 92 | -(NSURL *)recordFilePath{ 93 | return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp"]]; 94 | } 95 | @end 96 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/TLVoicePlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLVoicePlayer.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface TLVoicePlayer : NSObject 13 | +(TLVoicePlayer *)sharePlayer; 14 | -(void)playVoiceWithData:(NSData *)data didFinish:(void (^)(void))didFinish; 15 | -(void)endPlay; 16 | @end 17 | -------------------------------------------------------------------------------- /TLMessageView/Chat/Vendor/TLVoicePlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLVoicePlayer.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLVoicePlayer.h" 10 | #import 11 | 12 | @interface TLVoicePlayer () 13 | @property (nonatomic,strong)AVAudioPlayer *player; 14 | @property (nonatomic,copy)void (^didFinishBlock)(void); 15 | @end 16 | 17 | @implementation TLVoicePlayer 18 | +(TLVoicePlayer *)sharePlayer{ 19 | static dispatch_once_t onceToken; 20 | static TLVoicePlayer *player; 21 | dispatch_once(&onceToken, ^{ 22 | player = [[TLVoicePlayer alloc] init]; 23 | }); 24 | return player; 25 | } 26 | -(instancetype)init{ 27 | if (self = [super init]) { 28 | [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: nil]; 29 | } 30 | return self; 31 | } 32 | -(void)playVoiceWithData:(NSData *)data didFinish:(void (^)(void))didFinish{ 33 | if (self.player) { 34 | [self.player stop]; 35 | self.player.delegate = nil; 36 | self.player = nil; 37 | } 38 | self.didFinishBlock = didFinish; 39 | NSError *error; 40 | self.player = [[AVAudioPlayer alloc] initWithData:data error:&error]; 41 | self.player.volume = 1.0f; 42 | if (error) NSLog(@"%@", error); 43 | self.player.delegate = self; 44 | [self.player play]; 45 | } 46 | -(void)endPlay{ 47 | if (self.player && self.player.isPlaying) { 48 | [self.player stop]; 49 | } 50 | } 51 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ 52 | if (self.didFinishBlock) self.didFinishBlock(); 53 | } 54 | @end 55 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLButton.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/22. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TLButton : UIButton 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLButton.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/22. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLButton.h" 10 | 11 | @implementation TLButton 12 | /** 13 | * 放大热区 14 | * 15 | */ 16 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event{ 17 | CGRect bounds = self.bounds; 18 | CGFloat widthDelta = MAX(44.0 - bounds.size.width, 0); 19 | CGFloat heightDelta = MAX(44.0 - bounds.size.height, 0); 20 | bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta); 21 | return CGRectContainsPoint(bounds, point); 22 | } 23 | @end 24 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLChatEmojiBoard.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLChatEmojiBoard.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 2016/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static NSInteger emojiBoardHeight = 223; 12 | 13 | @protocol TLChatEmojiBoardDelegate 14 | -(void)chatEmojiBoarDidSelectEmoji:(NSString *)emoji; 15 | -(void)chatEmojiBoarDidClickBackspace; 16 | -(void)chatEmojiBoarDidClickSend; 17 | @end 18 | 19 | @interface TLChatEmojiBoard : UIView 20 | @property(nonatomic,assign)BOOL show; 21 | @property(nonatomic,assign)id delegate; 22 | @end 23 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLChatEmojiBoard.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLChatEmojiBoard.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 2016/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLChatEmojiBoard.h" 10 | #import "TLProjectMacro.h" 11 | #import "TLButton.h" 12 | 13 | @interface TLChatEmojiBoard () 14 | @property(nonatomic,strong)UIScrollView *pageScrollView; 15 | @property(nonatomic,strong)UIView *bottomView; 16 | @property(nonatomic,strong)UIButton *emojiSwitchBtn; 17 | @property(nonatomic,strong)UIButton *sendBtn; 18 | @property(nonatomic,strong)UIPageControl *pageControl; 19 | @property(nonatomic,strong)NSMutableArray *pageViews; 20 | @property(nonatomic,strong)NSArray *emojis; 21 | @end 22 | 23 | @implementation TLChatEmojiBoard 24 | { 25 | BOOL hasLayout; 26 | } 27 | -(instancetype)init{ 28 | if (self = [super init]) { 29 | [self addSubview:self.pageScrollView]; 30 | [self.pageScrollView mas_makeConstraints:^(MASConstraintMaker *make) { 31 | make.left.equalTo(self.mas_left).offset(0); 32 | make.right.equalTo(self.mas_right).offset(0); 33 | make.top.equalTo(self.mas_top).offset(0); 34 | }]; 35 | 36 | [self addSubview:self.bottomView]; 37 | [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) { 38 | make.left.equalTo(self.mas_left).offset(0); 39 | make.right.equalTo(self.mas_right).offset(0); 40 | make.top.equalTo(self.pageScrollView.mas_bottom).offset(0); 41 | make.bottom.equalTo(self.mas_bottom).offset(0); 42 | make.height.mas_offset(@37); 43 | }]; 44 | 45 | [self.bottomView addSubview:self.emojiSwitchBtn]; 46 | [self.emojiSwitchBtn mas_makeConstraints:^(MASConstraintMaker *make) { 47 | make.left.equalTo(self.bottomView.mas_left).offset(0); 48 | make.top.equalTo(self.bottomView.mas_top).offset(0); 49 | make.bottom.equalTo(self.bottomView.mas_bottom).offset(0); 50 | make.width.equalTo(self.emojiSwitchBtn.mas_height); 51 | }]; 52 | 53 | [self.bottomView addSubview:self.sendBtn]; 54 | [self.sendBtn mas_makeConstraints:^(MASConstraintMaker *make) { 55 | make.right.equalTo(self.bottomView.mas_right).offset(0); 56 | make.top.equalTo(self.bottomView.mas_top).offset(0); 57 | make.bottom.equalTo(self.bottomView.mas_bottom).offset(0); 58 | make.width.mas_offset(@52); 59 | }]; 60 | 61 | self.pageViews = [NSMutableArray array]; 62 | for (int i = 0; i < self.emojis.count; i ++ ) { 63 | NSArray *emojiArray = self.emojis[i]; 64 | UIView *pageView = [[UIView alloc] init]; 65 | [self.pageScrollView addSubview:pageView]; 66 | [self.pageViews addObject:pageView]; 67 | 68 | for (int j = 0; j < emojiArray.count; j ++ ) { 69 | TLButton *emojiBtn = [TLButton buttonWithType:UIButtonTypeCustom]; 70 | [emojiBtn setTitle:emojiArray[j] forState:UIControlStateNormal]; 71 | [pageView addSubview:emojiBtn]; 72 | [emojiBtn addTarget:self action:@selector(didClickEmoji:) forControlEvents:UIControlEventTouchUpInside]; 73 | if (j == emojiArray.count - 1) { 74 | [emojiBtn setImage:[UIImage imageNamed:@"emoji_btn_delete"] forState:UIControlStateNormal]; 75 | emojiBtn.tag = 99; 76 | } 77 | } 78 | } 79 | 80 | self.pageControl.numberOfPages = self.emojis.count; 81 | [self addSubview:self.pageControl]; 82 | [self.pageControl mas_makeConstraints:^(MASConstraintMaker *make) { 83 | make.centerX.equalTo(self.mas_centerX).offset(0); 84 | make.bottom.equalTo(self.pageScrollView.mas_bottom).offset(0); 85 | }]; 86 | } 87 | return self; 88 | } 89 | -(void)didClickEmoji:(UIButton *)sender{ 90 | if (sender.tag == 99) { 91 | if ([self.delegate respondsToSelector:@selector(chatEmojiBoarDidClickBackspace)]) { 92 | [self.delegate chatEmojiBoarDidClickBackspace]; 93 | } 94 | }else{ 95 | NSString *emoji = sender.titleLabel.text; 96 | if ([self.delegate respondsToSelector:@selector(chatEmojiBoarDidSelectEmoji:)]) { 97 | [self.delegate chatEmojiBoarDidSelectEmoji:emoji]; 98 | } 99 | } 100 | } 101 | -(void)didClickSend:(UIButton *)sender{ 102 | if ([self.delegate respondsToSelector:@selector(chatEmojiBoarDidClickSend)]) { 103 | [self.delegate chatEmojiBoarDidClickSend]; 104 | } 105 | } 106 | -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 107 | self.pageControl.currentPage = scrollView.contentOffset.x / scrollView.frame.size.width; 108 | } 109 | -(void)layoutSubviews{ 110 | [super layoutSubviews]; 111 | 112 | if (hasLayout) { 113 | return; 114 | } 115 | hasLayout = YES; 116 | 117 | CGSize emojiBtnSize = CGSizeMake(30, 34); 118 | 119 | //横向间距 120 | CGFloat emojiHorizontalSpacing = (self.frame.size.width - 50 - emojiBtnSize.width * 8) / 7.0; 121 | //纵向间距 122 | CGFloat emojiVerticalSpacing = (self.pageScrollView.frame.size.height - 25 - self.pageControl.frame.size.height - emojiBtnSize.height * 3) / 2.0; 123 | 124 | UIView *lastView = nil; 125 | for (int i = 0; i < self.pageViews.count; i ++ ) { 126 | UIView *pageView = self.pageViews[i]; 127 | 128 | if (!lastView) { 129 | [pageView mas_makeConstraints:^(MASConstraintMaker *make) { 130 | make.left.equalTo(self.pageScrollView.mas_left).offset(0); 131 | }]; 132 | }else{ 133 | [pageView mas_makeConstraints:^(MASConstraintMaker *make) { 134 | make.left.equalTo(lastView.mas_right).offset(0); 135 | }]; 136 | if (i == self.pageViews.count - 1) { 137 | [pageView mas_makeConstraints:^(MASConstraintMaker *make) { 138 | make.right.equalTo(self.pageScrollView.mas_right).offset(0); 139 | }]; 140 | } 141 | } 142 | 143 | [pageView mas_makeConstraints:^(MASConstraintMaker *make) { 144 | make.top.equalTo(self.pageScrollView.mas_top).offset(0); 145 | make.bottom.equalTo(self.pageScrollView.mas_bottom).offset(0); 146 | make.height.equalTo(self.pageScrollView.mas_height); 147 | make.width.equalTo(self.mas_width); 148 | }]; 149 | 150 | UIView *lastBtn = nil; 151 | for (int j = 0; j < pageView.subviews.count; j ++ ) { 152 | UIButton *emojiBtn = pageView.subviews[j]; 153 | if (j % 8 == 0) { 154 | [emojiBtn mas_makeConstraints:^(MASConstraintMaker *make) { 155 | make.left.equalTo(pageView.mas_left).offset(25); 156 | if (lastBtn) { 157 | make.top.equalTo(lastBtn.mas_bottom).offset(emojiVerticalSpacing); 158 | }else{ 159 | make.top.equalTo(pageView.mas_top).offset(25); 160 | } 161 | }]; 162 | 163 | }else{ 164 | [emojiBtn mas_makeConstraints:^(MASConstraintMaker *make) { 165 | make.left.equalTo(lastBtn.mas_right).offset(emojiHorizontalSpacing); 166 | make.top.equalTo(lastBtn.mas_top).offset(0); 167 | }]; 168 | } 169 | [emojiBtn mas_makeConstraints:^(MASConstraintMaker *make) { 170 | make.size.mas_offset(emojiBtnSize); 171 | }]; 172 | lastBtn = emojiBtn; 173 | } 174 | lastView = pageView; 175 | } 176 | } 177 | -(NSArray *)emojis{ 178 | if (!_emojis) { 179 | _emojis = @[ 180 | @[@"😊",@"😨",@"😍",@"😳",@"😎",@"😭",@"😌",@"😵",@"😴",@"😢",@"😅",@"😡",@"😜",@"😀",@"😲",@"😟",@"😤",@"😞",@"😫",@"😣",@"😈",@"😉",@"😯",@""], 181 | @[@"😕",@"😰",@"😋",@"😝",@"😓",@"😀",@"😂",@"😘",@"😒",@"😏",@"😶",@"😱",@"😖",@"😩",@"😔",@"😑",@"😚",@"😪",@"😇",@"🙊",@"👊",@"👎",@"☝️",@""], 182 | @[@"✌️",@"😬",@"😷",@"🙈",@"👌",@"👋",@"✊",@"💪",@"😆",@"☺️",@"🙉",@"👍",@"🙏",@"✋",@"☀️",@"☕️",@"⛄️",@"📚",@"🎁",@"🎉",@"🍦",@"☁️",@"❄️",@""], 183 | @[@"⚡️",@"💰",@"🎂",@"🎓",@"🍖",@"☔️",@"⛅️",@"✏️",@"💩",@"🎄",@"🍷",@"🎤",@"🏀",@"🀄️",@"💣",@"📢",@"🌍",@"🍫",@"🎲",@"🏂",@"💡",@"💤",@"🚫",@""], 184 | @[@"🌻",@"🍻",@"🎵",@"🏡",@"💢",@"📞",@"🚿",@"🍚",@"👪",@"👼",@"💊",@"🔫",@"🌹",@"🐶",@"💄",@"👫",@"👽",@"💋",@"🌙",@"🍉",@"🐷",@"💔",@"👻",@""], 185 | @[@"😈",@"💍",@"🌲",@"🐴",@"👑",@"🔥",@"⭐️",@"⚽️",@"🕖",@"⏰",@"😁",@"🚀",@"⏳",@""] 186 | ]; 187 | } 188 | return _emojis; 189 | } 190 | -(UIScrollView *)pageScrollView{ 191 | if (!_pageScrollView) { 192 | _pageScrollView = [[UIScrollView alloc] init]; 193 | _pageScrollView.pagingEnabled = YES; 194 | _pageScrollView.backgroundColor = UIColorFromRGB(0xf8f8f8); 195 | _pageScrollView.showsHorizontalScrollIndicator = NO; 196 | _pageScrollView.delegate = self; 197 | } 198 | return _pageScrollView; 199 | } 200 | -(UIPageControl *)pageControl{ 201 | if (!_pageControl) { 202 | _pageControl = [[UIPageControl alloc] init]; 203 | _pageControl.currentPageIndicatorTintColor = UIColorFromRGB(0x888888); 204 | _pageControl.pageIndicatorTintColor = UIColorFromRGB(0xb9b9b9); 205 | } 206 | return _pageControl; 207 | } 208 | -(UIView *)bottomView{ 209 | if (!_bottomView) { 210 | _bottomView = [[UIView alloc] init]; 211 | _bottomView.backgroundColor = UIColorFromRGB(0xffffff); 212 | } 213 | return _bottomView; 214 | } 215 | -(UIButton *)emojiSwitchBtn{ 216 | if (!_emojiSwitchBtn) { 217 | _emojiSwitchBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 218 | [_emojiSwitchBtn setImage:[UIImage imageNamed:@"emoji_btn_normal"] forState:UIControlStateNormal]; 219 | _emojiSwitchBtn.backgroundColor = UIColorFromRGB(0xf8f8f8); 220 | } 221 | return _emojiSwitchBtn; 222 | } 223 | -(UIButton *)sendBtn{ 224 | if (!_sendBtn) { 225 | _sendBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 226 | [_sendBtn setTitle:@"发送" forState:UIControlStateNormal]; 227 | [_sendBtn setTitleColor:UIColorFromRGB(0x9d9d9d) forState:UIControlStateNormal]; 228 | _sendBtn.titleLabel.font = [UIFont systemFontOfSize:13]; 229 | _sendBtn.backgroundColor = UIColorFromRGB(0xf8f8f8); 230 | [_sendBtn addTarget:self action:@selector(didClickSend:) forControlEvents:UIControlEventTouchUpInside]; 231 | } 232 | return _sendBtn; 233 | } 234 | @end 235 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLChatInputView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLChatInputView.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLChatViewController.h" 10 | #import "TLProjectMacro.h" 11 | 12 | @interface TLChatInputView : UIView 13 | -(instancetype)initWithChatVc:(TLChatViewController *)vc; 14 | @property(nonatomic,copy)void (^sendMsgAction)(RCMessageContent *x); 15 | @end 16 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLPhotoBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoBrowser.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/21. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TLImageScrollView; 12 | 13 | @protocol TLImageScrollViewDelegate 14 | @optional 15 | -(void)imageScrollViewLongTap:(UILongPressGestureRecognizer *)sender; 16 | -(void)imageScrollViewTap:(UITapGestureRecognizer *)sender; 17 | @end 18 | 19 | @interface TLPhotoBrowser : UIView 20 | @property(nonatomic,strong)TLImageScrollView *imageScrollView; 21 | +(void)showOriginalImage:(UIImage *)originalImage; 22 | @end 23 | 24 | 25 | @interface TLImageScrollView : UIScrollView 26 | @property(nonatomic,assign)id actionDelegate; 27 | @property(nonatomic,strong)UIImage *img; 28 | @end 29 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLPhotoBrowser.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLPhotoBrowser.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/21. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLPhotoBrowser.h" 10 | #import "TLProjectMacro.h" 11 | 12 | @interface TLPhotoBrowser () 13 | @end 14 | 15 | @implementation TLPhotoBrowser 16 | -(instancetype)initWithFrame:(CGRect)frame{ 17 | if (self = [super initWithFrame:frame]) { 18 | self.userInteractionEnabled = YES; 19 | self.backgroundColor = UIColorFromRGB(0x000000); 20 | 21 | [self addSubview:self.imageScrollView]; 22 | } 23 | return self; 24 | } 25 | +(void)showOriginalImage:(UIImage *)originalImage{ 26 | TLPhotoBrowser *browser = [[TLPhotoBrowser alloc] initWithFrame:kKeyWindow.bounds]; 27 | browser.imageScrollView.img = originalImage; 28 | [browser show]; 29 | } 30 | -(void)show{ 31 | self.alpha = 0; 32 | [kKeyWindow addSubview:self]; 33 | self.frame = kKeyWindow.bounds; 34 | 35 | [UIView animateWithDuration:0.25 animations:^{ 36 | self.alpha = 1; 37 | }]; 38 | } 39 | -(void)imageScrollViewLongTap:(UILongPressGestureRecognizer *)sender{ 40 | if (sender.state != UIGestureRecognizerStateBegan) { 41 | return; 42 | } 43 | UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"保存" otherButtonTitles:nil]; 44 | [sheet showInView:self]; 45 | } 46 | -(void)imageScrollViewTap:(UITapGestureRecognizer *)sender{ 47 | [UIView animateWithDuration:0.25 animations:^{ 48 | self.alpha = 0; 49 | } completion:^(BOOL finished) { 50 | [self removeFromSuperview]; 51 | }]; 52 | } 53 | -(TLImageScrollView *)imageScrollView{ 54 | if (!_imageScrollView) { 55 | _imageScrollView = [[TLImageScrollView alloc] initWithFrame:self.bounds]; 56 | _imageScrollView.actionDelegate = self; 57 | } 58 | return _imageScrollView; 59 | } 60 | @end 61 | 62 | 63 | 64 | 65 | @interface TLImageScrollView () 66 | @property(nonatomic,strong)UIImageView *photoView; 67 | @end 68 | 69 | @implementation TLImageScrollView 70 | -(instancetype)initWithFrame:(CGRect)frame{ 71 | if (self = [super initWithFrame:frame]) { 72 | self.delegate = self; 73 | self.minimumZoomScale = 0.8; 74 | self.maximumZoomScale = 2.0; 75 | 76 | [self addSubview:self.photoView]; 77 | 78 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressAction:)]; 79 | longPress.minimumPressDuration = 0.5; 80 | [self addGestureRecognizer:longPress]; 81 | 82 | UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTapAction:)]; 83 | [doubleTap setNumberOfTapsRequired:2]; 84 | [self addGestureRecognizer:doubleTap]; 85 | 86 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)]; 87 | [tap setNumberOfTapsRequired:1]; 88 | [self addGestureRecognizer:tap]; 89 | 90 | [tap requireGestureRecognizerToFail:doubleTap]; 91 | 92 | } 93 | return self; 94 | } 95 | -(void)doubleTapAction:(UITapGestureRecognizer *)sender{ 96 | [UIView animateWithDuration:0.25 animations:^{ 97 | if (self.zoomScale < 1) { 98 | self.zoomScale = 1.0f; 99 | }else if (self.zoomScale == 1){ 100 | self.zoomScale = 2.0f; 101 | }else{ 102 | self.zoomScale = 1.0f; 103 | } 104 | }]; 105 | } 106 | -(void)longPressAction:(UILongPressGestureRecognizer *)sender{ 107 | if ([self.actionDelegate respondsToSelector:@selector(imageScrollViewLongTap:)]) { 108 | [self.actionDelegate imageScrollViewLongTap:sender]; 109 | } 110 | } 111 | -(void)tapAction:(UITapGestureRecognizer *)sender{ 112 | if ([self.actionDelegate respondsToSelector:@selector(imageScrollViewTap:)]) { 113 | [self.actionDelegate imageScrollViewTap:sender]; 114 | } 115 | } 116 | -(void)setScrollZoom:(CGFloat)zoom{ 117 | self.zoomScale = 1; 118 | } 119 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView{ 120 | CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width)? 121 | (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0; 122 | 123 | CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height)? 124 | (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0; 125 | 126 | self.photoView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 127 | scrollView.contentSize.height * 0.5 + offsetY); 128 | } 129 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{ 130 | return self.photoView; 131 | } 132 | -(void)setImg:(UIImage *)img{ 133 | if (_img != img) { 134 | _img = img; 135 | self.photoView.image = img; 136 | } 137 | } 138 | -(UIImageView *)photoView{ 139 | if (!_photoView) { 140 | _photoView = [[UIImageView alloc] initWithFrame:self.bounds]; 141 | _photoView.userInteractionEnabled = YES; 142 | _photoView.contentMode = UIViewContentModeScaleAspectFit; 143 | } 144 | return _photoView; 145 | } 146 | @end 147 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLPluginBoardView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLPluginBoardView.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static NSInteger pluginBoardHeight = 223; 12 | 13 | @protocol TLPluginBoardViewDelegate 14 | -(NSArray *)pluginBoardItems; 15 | -(void)pluginBoardDidClickItemIndex:(NSInteger)itemIndex; 16 | @end 17 | 18 | @interface TLPluginBoardView : UIView 19 | @property(nonatomic,assign)BOOL show; 20 | @property(nonatomic,assign)id delegate; 21 | -(instancetype)initWithDelegate:(id )delegate; 22 | @end 23 | 24 | @interface TLPluginBoardItem : NSObject 25 | -(instancetype)initWithIcoNamed:(NSString *)icoNamed title:(NSString *)title; 26 | @property(nonatomic,copy)NSString *icoNamed; 27 | @property(nonatomic,copy)NSString *title; 28 | @end 29 | 30 | @interface TLPluginBoardButton : UIButton 31 | -(instancetype)initWithItem:(TLPluginBoardItem *)item; 32 | @end -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLPluginBoardView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLPluginBoardView.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLPluginBoardView.h" 10 | #import "TLProjectMacro.h" 11 | 12 | @interface TLPluginBoardView () 13 | @property(nonatomic,strong)NSMutableArray *btns; 14 | @end 15 | 16 | @implementation TLPluginBoardView 17 | { 18 | BOOL hasLayout; 19 | } 20 | -(instancetype)initWithDelegate:(id)delegate{ 21 | if (self = [super init]) { 22 | self.delegate = delegate; 23 | self.backgroundColor = UIColorFromRGB(0xF8F8F8); 24 | self.btns = [NSMutableArray array]; 25 | NSArray *items = [self.delegate pluginBoardItems]; 26 | for (int i = 0; i < items.count; i ++ ) { 27 | TLPluginBoardButton *btn = [[TLPluginBoardButton alloc] initWithItem:items[i]]; 28 | btn.tag = 100 + i; 29 | [self addSubview:btn]; 30 | [self.btns addObject:btn]; 31 | [btn addTarget:self action:@selector(didClickItem:) forControlEvents:UIControlEventTouchUpInside]; 32 | } 33 | } 34 | return self; 35 | } 36 | -(void)didClickItem:(TLPluginBoardButton *)sender{ 37 | if ([self.delegate respondsToSelector:@selector(pluginBoardDidClickItemIndex:)]) { 38 | [self.delegate pluginBoardDidClickItemIndex:sender.tag - 100]; 39 | } 40 | } 41 | -(void)layoutSubviews{ 42 | [super layoutSubviews]; 43 | 44 | if (hasLayout) { 45 | return; 46 | } 47 | hasLayout = YES; 48 | 49 | CGFloat icoSpacingWidth = (self.frame.size.width - 60 * 4) / 5; 50 | CGFloat icoSpacingHeight = (self.frame.size.height - 90 * 2) / 3; 51 | 52 | for (int i = 0; i < self.btns.count; i ++ ) { 53 | TLPluginBoardButton *btn = self.btns[i]; 54 | int k = i < 4 ? i : i - 4; 55 | [btn mas_makeConstraints:^(MASConstraintMaker *make) { 56 | make.left.equalTo(self.mas_left).offset((k + 1) * icoSpacingWidth + 60 * k); 57 | make.top.equalTo(self.mas_top).offset(i < 4 ? icoSpacingHeight : icoSpacingHeight * 2 + 90); 58 | make.size.mas_offset(CGSizeMake(60, 90)); 59 | }]; 60 | } 61 | } 62 | @end 63 | 64 | @implementation TLPluginBoardItem 65 | -(instancetype)initWithIcoNamed:(NSString *)icoNamed title:(NSString *)title{ 66 | if (self = [super init]) { 67 | self.icoNamed = icoNamed; 68 | self.title = title; 69 | } 70 | return self; 71 | } 72 | @end 73 | 74 | @implementation TLPluginBoardButton 75 | -(instancetype)initWithItem:(TLPluginBoardItem *)item{ 76 | if (self = [super init]) { 77 | [self setImage:[UIImage imageNamed:item.icoNamed] forState:UIControlStateNormal]; 78 | [self setTitle:item.title forState:UIControlStateNormal]; 79 | [self setTitleColor:UIColorFromRGB(0x6F7277) forState:UIControlStateNormal]; 80 | self.backgroundColor = [UIColor clearColor]; 81 | self.titleLabel.font = [UIFont systemFontOfSize:12]; 82 | 83 | } 84 | return self; 85 | } 86 | -(void)layoutSubviews{ 87 | [super layoutSubviews]; 88 | CGPoint center = self.imageView.center; 89 | center.x = self.frame.size.width / 2; 90 | center.y = self.imageView.frame.size.height / 2 + 5; 91 | self.imageView.center = center; 92 | 93 | CGRect newFrame = [self titleLabel].frame; 94 | newFrame.origin.x = 0; 95 | newFrame.origin.y = self.imageView.frame.size.height + 10; 96 | newFrame.size.width = self.frame.size.width; 97 | 98 | self.titleLabel.frame = newFrame; 99 | self.titleLabel.textAlignment = NSTextAlignmentCenter; 100 | } 101 | 102 | @end -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLRecordVoiceHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLRecordVoiceHUD.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TLRecordVoiceHUD : UIView 12 | @property(nonatomic,strong)UIButton *statusBtn; 13 | @property(nonatomic,strong)UIImageView *recordTipImage; 14 | 15 | +(void)updatePeakPower:(CGFloat)peakPower; 16 | +(void)showRecording; 17 | +(void)showWCancel; 18 | +(void)dismissWithRecordShort; 19 | +(void)dismiss; 20 | @end 21 | -------------------------------------------------------------------------------- /TLMessageView/Chat/View/TLRecordVoiceHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLRecordVoiceHUD.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/19. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import "TLRecordVoiceHUD.h" 10 | #import "TLProjectMacro.h" 11 | 12 | @interface TLRecordVoiceHUD () 13 | @end 14 | 15 | @implementation TLRecordVoiceHUD 16 | -(instancetype)initWithFrame:(CGRect)frame{ 17 | if (self = [super initWithFrame:frame]) { 18 | self.backgroundColor = UIColorFromRGBA(0x000000, 0.5); 19 | self.layer.cornerRadius = 4.0f; 20 | 21 | [self addSubview:self.recordTipImage]; 22 | [self.recordTipImage mas_makeConstraints:^(MASConstraintMaker *make) { 23 | make.top.equalTo(self.mas_top).offset(20); 24 | make.centerX.equalTo(self.mas_centerX).offset(0); 25 | make.size.mas_offset(CGSizeMake(90, 90)); 26 | }]; 27 | 28 | [self addSubview:self.statusBtn]; 29 | [self.statusBtn mas_makeConstraints:^(MASConstraintMaker *make) { 30 | make.left.equalTo(self.mas_left).offset(7); 31 | make.right.equalTo(self.mas_right).offset(-7); 32 | make.bottom.equalTo(self.mas_bottom).offset(-7); 33 | make.height.mas_offset(22); 34 | }]; 35 | 36 | self.tag = 999; 37 | } 38 | return self; 39 | } 40 | +(void)updatePeakPower:(CGFloat)peakPower{ 41 | TLRecordVoiceHUD *hud = [kKeyWindow viewWithTag:999]; 42 | if (!hud){ 43 | hud = [[TLRecordVoiceHUD alloc] initWithFrame:CGRectMake(0, 0, 150, 150)]; 44 | hud.center = kKeyWindow.center; 45 | [kKeyWindow addSubview:hud]; 46 | }; 47 | 48 | NSDictionary *dic = @{@[@0,@0.125]:@"voice_1",@[@0.126,@0.250]:@"voice_2",@[@0.251,@0.375]:@"voice_3",@[@0.376,@0.500]:@"voice_4",@[@0.501,@0.625]:@"voice_5",@[@0.626,@0.750]:@"voice_6",@[@0.751,@0.875]:@"voice_7",@[@0.876,@1]:@"voice_8"}; 49 | 50 | NSString *value; 51 | for (NSArray *key in dic) { 52 | if (peakPower < [key[1] floatValue] && peakPower > [key[0] floatValue]) { 53 | value = dic[key]; 54 | break; 55 | } 56 | } 57 | if (!value) { 58 | return; 59 | } 60 | 61 | hud.recordTipImage.image = [UIImage imageNamed:value]; 62 | } 63 | +(void)showRecording{ 64 | TLRecordVoiceHUD *hud = [kKeyWindow viewWithTag:999]; 65 | if (!hud){ 66 | hud = [[TLRecordVoiceHUD alloc] initWithFrame:CGRectMake(0, 0, 150, 150)]; 67 | hud.center = kKeyWindow.center; 68 | [kKeyWindow addSubview:hud]; 69 | 70 | hud.alpha = 0; 71 | [UIView animateWithDuration:0.1 animations:^{ 72 | hud.alpha = 1; 73 | }]; 74 | }; 75 | 76 | hud.statusBtn.selected = NO; 77 | [hud.statusBtn setTitle:@"手指上划,取消发送" forState:UIControlStateNormal]; 78 | } 79 | +(void)showWCancel{ 80 | TLRecordVoiceHUD *hud = [kKeyWindow viewWithTag:999]; 81 | hud.recordTipImage.image = [UIImage imageNamed:@"return"]; 82 | hud.statusBtn.selected = YES; 83 | [hud.statusBtn setTitle:@"松开手指,取消发送" forState:UIControlStateNormal]; 84 | } 85 | +(void)dismiss{ 86 | TLRecordVoiceHUD *hud = [kKeyWindow viewWithTag:999]; 87 | [UIView animateWithDuration:0.1 animations:^{ 88 | hud.alpha = 0; 89 | } completion:^(BOOL finished) { 90 | [hud removeFromSuperview]; 91 | }]; 92 | } 93 | +(void)dismissWithRecordShort{ 94 | TLRecordVoiceHUD *hud = [kKeyWindow viewWithTag:999]; 95 | hud.recordTipImage.image = [UIImage imageNamed:@"return"]; 96 | [hud.statusBtn setTitle:@"录音时间短" forState:UIControlStateNormal]; 97 | hud.statusBtn.selected = NO; 98 | 99 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 100 | [UIView animateWithDuration:0.3 animations:^{ 101 | hud.alpha = 0; 102 | } completion:^(BOOL finished) { 103 | [hud removeFromSuperview]; 104 | }]; 105 | }); 106 | } 107 | -(UIImageView *)recordTipImage{ 108 | if (!_recordTipImage) { 109 | _recordTipImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"voice_1"]]; 110 | } 111 | return _recordTipImage; 112 | } 113 | -(UIButton *)statusBtn{ 114 | if (!_statusBtn) { 115 | _statusBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 116 | _statusBtn.userInteractionEnabled = NO; 117 | _statusBtn.titleLabel.font = [UIFont systemFontOfSize:13.5]; 118 | [_statusBtn setBackgroundImage:[UIImage imageNamed:@"red_background"] forState:UIControlStateSelected]; 119 | } 120 | return _statusBtn; 121 | } 122 | @end 123 | -------------------------------------------------------------------------------- /TLMessageView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | 此 App 需要您的同意才能使用相机 7 | NSMicrophoneUsageDescription 8 | 此 App 需要您的同意才能使用麦克风 9 | NSPhotoLibraryUsageDescription 10 | 此 App 需要您的同意才能读取媒体资料库 11 | NSLocationAlwaysUsageDescription 12 | 允许获取位置信息 13 | NSLocationWhenInUseUsageDescription 14 | 允许获取位置信息 15 | CFBundleDevelopmentRegion 16 | zh_CN 17 | CFBundleExecutable 18 | $(EXECUTABLE_NAME) 19 | CFBundleIdentifier 20 | $(PRODUCT_BUNDLE_IDENTIFIER) 21 | CFBundleInfoDictionaryVersion 22 | 6.0 23 | CFBundleName 24 | $(PRODUCT_NAME) 25 | CFBundlePackageType 26 | APPL 27 | CFBundleShortVersionString 28 | 1.0 29 | CFBundleSignature 30 | ???? 31 | CFBundleVersion 32 | 1 33 | LSRequiresIPhoneOS 34 | 35 | NSAppTransportSecurity 36 | 37 | NSAllowsArbitraryLoads 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /TLMessageView/TLProjectMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // Header.h 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #ifndef Header_h 10 | #define Header_h 11 | 12 | #import 13 | #import 14 | #import 15 | 16 | /** 17 | * Weaky self 18 | */ 19 | #define weakifySelf \ 20 | __weak __typeof(&*self)weakSelf = self; 21 | 22 | #define strongifySelf \ 23 | __strong __typeof(&*weakSelf)self = weakSelf; 24 | 25 | #define kKeyWindow [UIApplication sharedApplication].keyWindow 26 | 27 | /** 28 | * Color 29 | */ 30 | #define ColorWithRGBA(r, g, b, a) [UIColor colorWithRed:(r) / 255.0f green:(g) / 255.0f blue:(b) / 255.0f alpha:(a)] 31 | 32 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 33 | 34 | #define UIColorFromRGBA(rgbValue, a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:a] 35 | 36 | #define UI_SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width) 37 | #define UI_SCREEN_HEIGHT ([[UIScreen mainScreen] bounds].size.height) 38 | #define UI_SCREEN_BOUNDS ([UIScreen mainScreen].bounds) 39 | 40 | #endif /* Header_h */ 41 | -------------------------------------------------------------------------------- /TLMessageView/account.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0001 6 | XazzEyo80CdGQT0ixnyRcpp+gB9O3Igt57fet/zpar+Mvfmwptdl0to2K5YBcxcCBOmM2CJONymZaaNbhcd8yA== 7 | 0002 8 | 7Shy0aKksceAEqUpX0xUa0xpZ1Jg7vO5lNpY4hvHdVrHW6Ulb2kykOebNwPDLayFWQJ0rg0l3+lBJO1Gpvm3cw== 9 | 0003 10 | FI8WkREyWcSM//7QcJJDbUxpZ1Jg7vO5lNpY4hvHdVrHW6Ulb2kykE8HFc0iqv8aWQJ0rg0l3+kgZJLu6vmX1w== 11 | 0004 12 | F3fH0ar+RQDpd1KR1kYEcpp+gB9O3Igt57fet/zpar+Mvfmwptdl0rjOu1XS89iO/1qQdTSocbhLMJ4ui1RA/Q== 13 | 0005 14 | jED3KUMVIkJGPRY16y3otpp+gB9O3Igt57fet/zpar+Mvfmwptdl0kQRZzs+t2vn/1qQdTSocbitvzrbMsmLQg== 15 | 0006 16 | QWf/T/ykIryE68yrgR9NQkxpZ1Jg7vO5lNpY4hvHdVrHW6Ulb2kykFNw0mLFyYquJYKPHr0CS2hBJO1Gpvm3cw== 17 | 0007 18 | 7Pj+49opgqiXnB8zBJ7iVJp+gB9O3Igt57fet/zpar+Mvfmwptdl0oGFMFvHzLa5BOmM2CJONyl2gWGIv369NA== 19 | 0008 20 | Pjsy4MDskV13FgnWNP1JOeSZkEN9cVfzRVKTg8tY0IOhDwJ1Cn3qxVgN5+mt2W4a2vylG+P0/9ipZCgp45dC1Q== 21 | 0009 22 | 06pyYHUDLPadREnKdilFg5p+gB9O3Igt57fet/zpar+Mvfmwptdl0p1Ns7ESnQi9oiyf2BfJawKvWqlA8EwXAw== 23 | 0010 24 | DM+6m33yv15sOdkiCdtgr0xpZ1Jg7vO5lNpY4hvHdVrHW6Ulb2kykMgLiBiSW/0FkK1CZPbBalJrh1SYaXka8g== 25 | 26 | 27 | -------------------------------------------------------------------------------- /TLMessageView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TLMessageView 4 | // 5 | // Created by 郭锐 on 16/8/18. 6 | // Copyright © 2016年 com.garry.message. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------