├── README.md ├── YTChatDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── YTChatDemo.xccheckout │ └── xcuserdata │ │ └── Ycc.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── Ycc.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── YTChatDemo.xcscheme │ └── xcschememanagement.plist ├── YTChatDemo ├── HUD │ ├── MBProgressHUD.h │ └── MBProgressHUD.m ├── ImgBrower │ ├── YTImgBrowerController.h │ ├── YTImgBrowerController.m │ ├── YTImgInfo.h │ ├── YTImgInfo.m │ ├── YTImgScroll.h │ └── YTImgScroll.m ├── KeyBoard │ ├── Emoji │ │ ├── EmojiModel │ │ │ ├── Resource │ │ │ │ ├── EmojiFile.plist │ │ │ │ ├── EmojiGirl.plist │ │ │ │ ├── EmojiPanda.plist │ │ │ │ ├── EmojiSutra.plist │ │ │ │ ├── girl_beg@2x.gif │ │ │ │ ├── grinning@2x.png │ │ │ │ └── smile@2x.png │ │ │ ├── YTEmoji.h │ │ │ ├── YTEmoji.m │ │ │ ├── YTEmojiChartletM.h │ │ │ ├── YTEmojiChartletM.m │ │ │ ├── YTEmojiFile.h │ │ │ ├── YTEmojiFile.m │ │ │ ├── YTEmojiIconM.h │ │ │ ├── YTEmojiIconM.m │ │ │ ├── YTEmojiM.h │ │ │ └── YTEmojiM.m │ │ └── EmojiView │ │ │ ├── YTEmojiBottom.h │ │ │ ├── YTEmojiBottom.m │ │ │ ├── YTEmojiButton.h │ │ │ ├── YTEmojiButton.m │ │ │ ├── YTEmojiPage.h │ │ │ ├── YTEmojiPage.m │ │ │ ├── YTEmojiView.h │ │ │ └── YTEmojiView.m │ ├── More │ │ ├── YTMoreView.h │ │ └── YTMoreView.m │ ├── Other │ │ ├── SLGrowingTextView.h │ │ ├── SLGrowingTextView.m │ │ ├── YTTextAttachment.h │ │ ├── YTTextAttachment.m │ │ ├── YTTextView.h │ │ └── YTTextView.m │ ├── YTKeyBoardView.h │ └── YTKeyBoardView.m ├── Main │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── StartVC.h │ └── StartVC.m ├── Supporting Files │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Defines.h │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ └── YT@2x.png │ │ ├── btn_del.imageset │ │ │ ├── Contents.json │ │ │ └── btn_facecancel@2x.png │ │ ├── btn_face.imageset │ │ │ ├── Contents.json │ │ │ ├── btn_face@2x.png │ │ │ └── btn_face@3x.png │ │ ├── btn_key.imageset │ │ │ ├── Contents.json │ │ │ ├── btn_key@2x.png │ │ │ └── btn_key@3x.png │ │ ├── btn_more.imageset │ │ │ ├── Contents.json │ │ │ ├── btn_chatmore@2x.png │ │ │ └── btn_chatmore@3x.png │ │ ├── btn_photo.imageset │ │ │ ├── Contents.json │ │ │ ├── btn_photo@2x.png │ │ │ └── btn_photo@3x.png │ │ ├── btn_pic.imageset │ │ │ ├── Contents.json │ │ │ ├── btn_pic@2x.png │ │ │ └── btn_pic@3x.png │ │ ├── btn_say.imageset │ │ │ ├── Contents.json │ │ │ ├── btn_say@2x.png │ │ │ └── btn_say@3x.png │ │ ├── camera_down.imageset │ │ │ ├── Contents.json │ │ │ └── camera_down@2x.png │ │ ├── camera_nomal.imageset │ │ │ ├── Contents.json │ │ │ └── camera@2x.png │ │ ├── default_img.imageset │ │ │ ├── Contents.json │ │ │ └── default_img@2x.png │ │ ├── photo_down.imageset │ │ │ ├── Contents.json │ │ │ └── img_down@2x.png │ │ └── photo_nomal.imageset │ │ │ ├── Contents.json │ │ │ └── img@2x.png │ ├── Info.plist │ ├── Message.h │ ├── Message.m │ ├── YTChatDemo.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── YTChatDemo.xcdatamodel │ │ │ └── contents │ └── main.m └── Tools │ ├── Catgray │ ├── NSDictionary+YTSafe.h │ ├── NSDictionary+YTSafe.m │ ├── UIColor+YTBacal.h │ ├── UIColor+YTBacal.m │ ├── UIColor+YTKeyBoard.h │ ├── UIColor+YTKeyBoard.m │ ├── UIImage+YTGif.h │ ├── UIImage+YTGif.m │ ├── UIView+YTLayer.h │ └── UIView+YTLayer.m │ ├── coreData │ ├── NSManagedObject+YTMoreThread.h │ ├── NSManagedObject+YTMoreThread.m │ ├── YTCoreData.h │ └── YTCoreData.m │ └── other │ ├── YTDeviceTest.h │ ├── YTDeviceTest.m │ ├── YTNetworkLoad.h │ └── YTNetworkLoad.m └── YTChatDemoTests ├── Info.plist └── YTChatDemoTests.m /README.md: -------------------------------------------------------------------------------- 1 | # KeyBoard 2 | 3 | -------------------------------------------------------------------------------- /YTChatDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /YTChatDemo.xcodeproj/project.xcworkspace/xcshareddata/YTChatDemo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | FE6A357C-A451-4510-804D-CB75BC0DED4D 9 | IDESourceControlProjectName 10 | YTChatDemo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 903E540FEA9652A10B393974C6BBBEC6EB5C8454 14 | https://github.com/TiYcc/KeyBoard.git 15 | 16 | IDESourceControlProjectPath 17 | YTChatDemo.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 903E540FEA9652A10B393974C6BBBEC6EB5C8454 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/TiYcc/KeyBoard.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 903E540FEA9652A10B393974C6BBBEC6EB5C8454 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 903E540FEA9652A10B393974C6BBBEC6EB5C8454 36 | IDESourceControlWCCName 37 | KeyBaoard 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /YTChatDemo.xcodeproj/project.xcworkspace/xcuserdata/Ycc.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo.xcodeproj/project.xcworkspace/xcuserdata/Ycc.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YTChatDemo.xcodeproj/xcuserdata/Ycc.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /YTChatDemo.xcodeproj/xcuserdata/Ycc.xcuserdatad/xcschemes/YTChatDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /YTChatDemo.xcodeproj/xcuserdata/Ycc.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YTChatDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 50724F581B8B0EDB00EA425A 16 | 17 | primary 18 | 19 | 20 | 50724F741B8B0EDB00EA425A 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YTChatDemo/HUD/MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.h 3 | // Version 0.9.1 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | // This code is distributed under the terms and conditions of the MIT license. 8 | 9 | // Copyright (c) 2009-2015 Matej Bukovinski 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | #import 30 | #import 31 | #import 32 | 33 | @protocol MBProgressHUDDelegate; 34 | 35 | 36 | typedef NS_ENUM(NSInteger, MBProgressHUDMode) { 37 | /** Progress is shown using an UIActivityIndicatorView. This is the default. */ 38 | MBProgressHUDModeIndeterminate, 39 | /** Progress is shown using a round, pie-chart like, progress view. */ 40 | MBProgressHUDModeDeterminate, 41 | /** Progress is shown using a horizontal progress bar */ 42 | MBProgressHUDModeDeterminateHorizontalBar, 43 | /** Progress is shown using a ring-shaped progress view. */ 44 | MBProgressHUDModeAnnularDeterminate, 45 | /** Shows a custom view */ 46 | MBProgressHUDModeCustomView, 47 | /** Shows only labels */ 48 | MBProgressHUDModeText 49 | }; 50 | 51 | typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { 52 | /** Opacity animation */ 53 | MBProgressHUDAnimationFade, 54 | /** Opacity + scale animation */ 55 | MBProgressHUDAnimationZoom, 56 | MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, 57 | MBProgressHUDAnimationZoomIn 58 | }; 59 | 60 | 61 | #ifndef MB_INSTANCETYPE 62 | #if __has_feature(objc_instancetype) 63 | #define MB_INSTANCETYPE instancetype 64 | #else 65 | #define MB_INSTANCETYPE id 66 | #endif 67 | #endif 68 | 69 | #ifndef MB_STRONG 70 | #if __has_feature(objc_arc) 71 | #define MB_STRONG strong 72 | #else 73 | #define MB_STRONG retain 74 | #endif 75 | #endif 76 | 77 | #ifndef MB_WEAK 78 | #if __has_feature(objc_arc_weak) 79 | #define MB_WEAK weak 80 | #elif __has_feature(objc_arc) 81 | #define MB_WEAK unsafe_unretained 82 | #else 83 | #define MB_WEAK assign 84 | #endif 85 | #endif 86 | 87 | #if NS_BLOCKS_AVAILABLE 88 | typedef void (^MBProgressHUDCompletionBlock)(); 89 | #endif 90 | 91 | 92 | /** 93 | * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. 94 | * 95 | * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. 96 | * The MBProgressHUD window spans over the entire space given to it by the initWithFrame constructor and catches all 97 | * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is 98 | * drawn centered as a rounded semi-transparent view which resizes depending on the user specified content. 99 | * 100 | * This view supports four modes of operation: 101 | * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView 102 | * - MBProgressHUDModeDeterminate - shows a custom round progress indicator 103 | * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator 104 | * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (see `customView`) 105 | * 106 | * All three modes can have optional labels assigned: 107 | * - If the labelText property is set and non-empty then a label containing the provided content is placed below the 108 | * indicator view. 109 | * - If also the detailsLabelText property is set then another label is placed below the first label. 110 | */ 111 | @interface MBProgressHUD : UIView 112 | 113 | /** 114 | * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. 115 | * 116 | * @note This method sets `removeFromSuperViewOnHide`. The HUD will automatically be removed from the view hierarchy when hidden. 117 | * 118 | * @param view The view that the HUD will be added to 119 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 120 | * animations while appearing. 121 | * @return A reference to the created HUD. 122 | * 123 | * @see hideHUDForView:animated: 124 | * @see animationType 125 | */ 126 | + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; 127 | 128 | /** 129 | * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. 130 | * 131 | * @note This method sets `removeFromSuperViewOnHide`. The HUD will automatically be removed from the view hierarchy when hidden. 132 | * 133 | * @param view The view that is going to be searched for a HUD subview. 134 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 135 | * animations while disappearing. 136 | * @return YES if a HUD was found and removed, NO otherwise. 137 | * 138 | * @see showHUDAddedTo:animated: 139 | * @see animationType 140 | */ 141 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; 142 | 143 | /** 144 | * Finds all the HUD subviews and hides them. 145 | * 146 | * @note This method sets `removeFromSuperViewOnHide`. The HUDs will automatically be removed from the view hierarchy when hidden. 147 | * 148 | * @param view The view that is going to be searched for HUD subviews. 149 | * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use 150 | * animations while disappearing. 151 | * @return the number of HUDs found and removed. 152 | * 153 | * @see hideHUDForView:animated: 154 | * @see animationType 155 | */ 156 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; 157 | 158 | /** 159 | * Finds the top-most HUD subview and returns it. 160 | * 161 | * @param view The view that is going to be searched. 162 | * @return A reference to the last HUD subview discovered. 163 | */ 164 | + (MB_INSTANCETYPE)HUDForView:(UIView *)view; 165 | 166 | /** 167 | * Finds all HUD subviews and returns them. 168 | * 169 | * @param view The view that is going to be searched. 170 | * @return All found HUD views (array of MBProgressHUD objects). 171 | */ 172 | + (NSArray *)allHUDsForView:(UIView *)view; 173 | 174 | /** 175 | * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with 176 | * window.bounds as the parameter. 177 | * 178 | * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as 179 | * the HUD's superview (i.e., the window that the HUD will be added to). 180 | */ 181 | - (id)initWithWindow:(UIWindow *)window; 182 | 183 | /** 184 | * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with 185 | * view.bounds as the parameter 186 | * 187 | * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as 188 | * the HUD's superview (i.e., the view that the HUD will be added to). 189 | */ 190 | - (id)initWithView:(UIView *)view; 191 | 192 | /** 193 | * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so 194 | * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread 195 | * (e.g., when using something like NSOperation or calling an asynchronous call like NSURLRequest). 196 | * 197 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 198 | * animations while appearing. 199 | * 200 | * @see animationType 201 | */ 202 | - (void)show:(BOOL)animated; 203 | 204 | /** 205 | * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 206 | * hide the HUD when your task completes. 207 | * 208 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 209 | * animations while disappearing. 210 | * 211 | * @see animationType 212 | */ 213 | - (void)hide:(BOOL)animated; 214 | 215 | /** 216 | * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 217 | * hide the HUD when your task completes. 218 | * 219 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 220 | * animations while disappearing. 221 | * @param delay Delay in seconds until the HUD is hidden. 222 | * 223 | * @see animationType 224 | */ 225 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; 226 | 227 | /** 228 | * Shows the HUD while a background task is executing in a new thread, then hides the HUD. 229 | * 230 | * This method also takes care of autorelease pools so your method does not have to be concerned with setting up a 231 | * pool. 232 | * 233 | * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. 234 | * @param target The object that the target method belongs to. 235 | * @param object An optional object to be passed to the method. 236 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use 237 | * animations while (dis)appearing. 238 | */ 239 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; 240 | 241 | #if NS_BLOCKS_AVAILABLE 242 | 243 | /** 244 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 245 | * 246 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 247 | */ 248 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; 249 | 250 | /** 251 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 252 | * 253 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 254 | */ 255 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; 256 | 257 | /** 258 | * Shows the HUD while a block is executing on the specified dispatch queue, then hides the HUD. 259 | * 260 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 261 | */ 262 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; 263 | 264 | /** 265 | * Shows the HUD while a block is executing on the specified dispatch queue, executes completion block on the main queue, and then hides the HUD. 266 | * 267 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will 268 | * not use animations while (dis)appearing. 269 | * @param block The block to be executed while the HUD is shown. 270 | * @param queue The dispatch queue on which the block should be executed. 271 | * @param completion The block to be executed on completion. 272 | * 273 | * @see completionBlock 274 | */ 275 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 276 | completionBlock:(MBProgressHUDCompletionBlock)completion; 277 | 278 | /** 279 | * A block that gets called after the HUD was completely hidden. 280 | */ 281 | @property (copy) MBProgressHUDCompletionBlock completionBlock; 282 | 283 | #endif 284 | 285 | /** 286 | * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. 287 | * 288 | * @see MBProgressHUDMode 289 | */ 290 | @property (assign) MBProgressHUDMode mode; 291 | 292 | /** 293 | * The animation type that should be used when the HUD is shown and hidden. 294 | * 295 | * @see MBProgressHUDAnimation 296 | */ 297 | @property (assign) MBProgressHUDAnimation animationType; 298 | 299 | /** 300 | * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. 301 | * For best results use a 37 by 37 pixel view (so the bounds match the built in indicator bounds). 302 | */ 303 | @property (MB_STRONG) UIView *customView; 304 | 305 | /** 306 | * The HUD delegate object. 307 | * 308 | * @see MBProgressHUDDelegate 309 | */ 310 | @property (MB_WEAK) id delegate; 311 | 312 | /** 313 | * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit 314 | * the entire text. If the text is too long it will get clipped by displaying "..." at the end. If left unchanged or 315 | * set to @"", then no message is displayed. 316 | */ 317 | @property (copy) NSString *labelText; 318 | 319 | /** 320 | * An optional details message displayed below the labelText message. This message is displayed only if the labelText 321 | * property is also set and is different from an empty string (@""). The details text can span multiple lines. 322 | */ 323 | @property (copy) NSString *detailsLabelText; 324 | 325 | /** 326 | * The opacity of the HUD window. Defaults to 0.8 (80% opacity). 327 | */ 328 | @property (assign) float opacity; 329 | 330 | /** 331 | * The color of the HUD window. Defaults to black. If this property is set, color is set using 332 | * this UIColor and the opacity property is not used. using retain because performing copy on 333 | * UIColor base colors (like [UIColor greenColor]) cause problems with the copyZone. 334 | */ 335 | @property (MB_STRONG) UIColor *color; 336 | 337 | /** 338 | * The x-axis offset of the HUD relative to the centre of the superview. 339 | */ 340 | @property (assign) float xOffset; 341 | 342 | /** 343 | * The y-axis offset of the HUD relative to the centre of the superview. 344 | */ 345 | @property (assign) float yOffset; 346 | 347 | /** 348 | * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). 349 | * Defaults to 20.0 350 | */ 351 | @property (assign) float margin; 352 | 353 | /** 354 | * The corner radius for the HUD 355 | * Defaults to 10.0 356 | */ 357 | @property (assign) float cornerRadius; 358 | 359 | /** 360 | * Cover the HUD background view with a radial gradient. 361 | */ 362 | @property (assign) BOOL dimBackground; 363 | 364 | /* 365 | * Grace period is the time (in seconds) that the invoked method may be run without 366 | * showing the HUD. If the task finishes before the grace time runs out, the HUD will 367 | * not be shown at all. 368 | * This may be used to prevent HUD display for very short tasks. 369 | * Defaults to 0 (no grace time). 370 | * Grace time functionality is only supported when the task status is known! 371 | * @see taskInProgress 372 | */ 373 | @property (assign) float graceTime; 374 | 375 | /** 376 | * The minimum time (in seconds) that the HUD is shown. 377 | * This avoids the problem of the HUD being shown and than instantly hidden. 378 | * Defaults to 0 (no minimum show time). 379 | */ 380 | @property (assign) float minShowTime; 381 | 382 | /** 383 | * Indicates that the executed operation is in progress. Needed for correct graceTime operation. 384 | * If you don't set a graceTime (different than 0.0) this does nothing. 385 | * This property is automatically set when using showWhileExecuting:onTarget:withObject:animated:. 386 | * When threading is done outside of the HUD (i.e., when the show: and hide: methods are used directly), 387 | * you need to set this property when your task starts and completes in order to have normal graceTime 388 | * functionality. 389 | */ 390 | @property (assign) BOOL taskInProgress; 391 | 392 | /** 393 | * Removes the HUD from its parent view when hidden. 394 | * Defaults to NO. 395 | */ 396 | @property (assign) BOOL removeFromSuperViewOnHide; 397 | 398 | /** 399 | * Font to be used for the main label. Set this property if the default is not adequate. 400 | */ 401 | @property (MB_STRONG) UIFont* labelFont; 402 | 403 | /** 404 | * Color to be used for the main label. Set this property if the default is not adequate. 405 | */ 406 | @property (MB_STRONG) UIColor* labelColor; 407 | 408 | /** 409 | * Font to be used for the details label. Set this property if the default is not adequate. 410 | */ 411 | @property (MB_STRONG) UIFont* detailsLabelFont; 412 | 413 | /** 414 | * Color to be used for the details label. Set this property if the default is not adequate. 415 | */ 416 | @property (MB_STRONG) UIColor* detailsLabelColor; 417 | 418 | /** 419 | * The color of the activity indicator. Defaults to [UIColor whiteColor] 420 | * Does nothing on pre iOS 5. 421 | */ 422 | @property (MB_STRONG) UIColor *activityIndicatorColor; 423 | 424 | /** 425 | * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. 426 | */ 427 | @property (assign) float progress; 428 | 429 | /** 430 | * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). 431 | */ 432 | @property (assign) CGSize minSize; 433 | 434 | 435 | /** 436 | * The actual size of the HUD bezel. 437 | * You can use this to limit touch handling on the bezel aria only. 438 | * @see https://github.com/jdg/MBProgressHUD/pull/200 439 | */ 440 | @property (atomic, assign, readonly) CGSize size; 441 | 442 | 443 | /** 444 | * Force the HUD dimensions to be equal if possible. 445 | */ 446 | @property (assign, getter = isSquare) BOOL square; 447 | 448 | @end 449 | 450 | 451 | @protocol MBProgressHUDDelegate 452 | 453 | @optional 454 | 455 | /** 456 | * Called after the HUD was fully hidden from the screen. 457 | */ 458 | - (void)hudWasHidden:(MBProgressHUD *)hud; 459 | 460 | @end 461 | 462 | 463 | /** 464 | * A progress view for showing definite progress by filling up a circle (pie chart). 465 | */ 466 | @interface MBRoundProgressView : UIView 467 | 468 | /** 469 | * Progress (0.0 to 1.0) 470 | */ 471 | @property (nonatomic, assign) float progress; 472 | 473 | /** 474 | * Indicator progress color. 475 | * Defaults to white [UIColor whiteColor] 476 | */ 477 | @property (nonatomic, MB_STRONG) UIColor *progressTintColor; 478 | 479 | /** 480 | * Indicator background (non-progress) color. 481 | * Defaults to translucent white (alpha 0.1) 482 | */ 483 | @property (nonatomic, MB_STRONG) UIColor *backgroundTintColor; 484 | 485 | /* 486 | * Display mode - NO = round or YES = annular. Defaults to round. 487 | */ 488 | @property (nonatomic, assign, getter = isAnnular) BOOL annular; 489 | 490 | @end 491 | 492 | 493 | /** 494 | * A flat bar progress view. 495 | */ 496 | @interface MBBarProgressView : UIView 497 | 498 | /** 499 | * Progress (0.0 to 1.0) 500 | */ 501 | @property (nonatomic, assign) float progress; 502 | 503 | /** 504 | * Bar border line color. 505 | * Defaults to white [UIColor whiteColor]. 506 | */ 507 | @property (nonatomic, MB_STRONG) UIColor *lineColor; 508 | 509 | /** 510 | * Bar background color. 511 | * Defaults to clear [UIColor clearColor]; 512 | */ 513 | @property (nonatomic, MB_STRONG) UIColor *progressRemainingColor; 514 | 515 | /** 516 | * Bar progress color. 517 | * Defaults to white [UIColor whiteColor]. 518 | */ 519 | @property (nonatomic, MB_STRONG) UIColor *progressColor; 520 | 521 | @end 522 | -------------------------------------------------------------------------------- /YTChatDemo/ImgBrower/YTImgBrowerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTImgBrowerController.h 3 | // YTImgBrower 4 | // 5 | // Created by TI on 15/10/26. 6 | // Copyright © 2015年 ycctime.com. All rights reserved. 7 | // 8 | 9 | #import 10 | @class YTImgInfo; 11 | typedef NS_ENUM(NSInteger, YTImgBrowerModel) { 12 | YTImgBrowerModelNone = 0, 13 | }; 14 | 15 | @protocol YTImgBrowerControllerDelegate 16 | @optional 17 | /** 图片信息初始化完成触发 */ 18 | - (void)imgBrowerControllerInitEnd:(YTImgInfo *)imgInfo; 19 | /** 在控制器消失之前触发 */ 20 | - (void)imgBrowerControllerWillDismiss:(YTImgInfo *)imgInfo; 21 | @end 22 | 23 | @interface YTImgBrowerController : UIViewController 24 | 25 | @property(nonatomic, assign) YTImgBrowerModel *model; 26 | /* 27 | * delegate 代理 28 | * imgInfos 里面是YTImgInfo对象 29 | * index 开始定位到第几张图片 默认是0 代表第一张 30 | */ 31 | - (instancetype)initWithDelegate:(id)delegate imgInfos:(NSArray *)imgInfos index:(NSInteger)index; 32 | @end 33 | -------------------------------------------------------------------------------- /YTChatDemo/ImgBrower/YTImgBrowerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTImgBrowerController.m 3 | // YTImgBrower 4 | // 5 | // Created by TI on 15/10/26. 6 | // Copyright © 2015年 ycctime.com. All rights reserved. 7 | // 8 | 9 | #import "YTImgBrowerController.h" 10 | #import "YTImgScroll.h" 11 | 12 | #define Page_Lab_H 14.0f 13 | #define Page_Scale (4.9f/5.0f) 14 | 15 | @interface YTImgBrowerController (){ 16 | NSInteger beginIndex, currentIndex;//当前显示图片下标 17 | YTImgScroll *currentScroll;//当前ImgScroll 18 | CGFloat frontPointX; 19 | } 20 | @property(nonatomic, assign) id delegate; 21 | @property(nonatomic, strong) NSArray *imgInfos; 22 | @property(nonatomic, strong) UIScrollView *scrollView; 23 | @property(nonatomic, strong) UILabel *pageNumber; 24 | 25 | @property(nonatomic, strong) NSMutableArray *imgScrolls; 26 | @end 27 | 28 | @implementation YTImgBrowerController 29 | 30 | - (UIStatusBarStyle)preferredStatusBarStyle{ 31 | return UIStatusBarStyleLightContent; 32 | } 33 | 34 | - (NSMutableArray *)imgScrolls{ 35 | if (!_imgScrolls) { 36 | _imgScrolls = [NSMutableArray arrayWithCapacity:2]; 37 | } 38 | return _imgScrolls; 39 | } 40 | 41 | - (void)viewDidLoad { 42 | [super viewDidLoad]; 43 | [self showImgBrower]; 44 | [self addGestureRecognizers]; 45 | } 46 | 47 | - (void)viewWillDisappear:(BOOL)animated{ 48 | [super viewWillDisappear:animated]; 49 | [self delegateWillDismiss]; 50 | } 51 | 52 | - (void)viewWillAppear:(BOOL)animated{ 53 | [super viewWillAppear:animated]; 54 | } 55 | 56 | - (instancetype)initWithDelegate:(id)delegate imgInfos:(NSArray *)imgInfos index:(NSInteger)index{ 57 | if (self = [super init]) { 58 | self.delegate = delegate; 59 | self.imgInfos = [imgInfos copy]; 60 | beginIndex = index; 61 | currentIndex = -1; 62 | [self performSelector:@selector(initEnd) withObject:nil afterDelay:0.1]; 63 | } 64 | return self; 65 | } 66 | 67 | - (UIScrollView *)scrollView{ 68 | if (!_scrollView) { 69 | UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:self.view.bounds]; 70 | scrollView.delegate = self; 71 | scrollView.pagingEnabled = YES; 72 | scrollView.alwaysBounceVertical = NO; 73 | scrollView.showsVerticalScrollIndicator = NO; 74 | scrollView.showsHorizontalScrollIndicator = NO; 75 | scrollView.backgroundColor = [UIColor blackColor]; 76 | scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 77 | [self.view addSubview:_scrollView = scrollView]; 78 | } 79 | return _scrollView; 80 | } 81 | 82 | - (UILabel *)pageNumber{ 83 | if (!_pageNumber) { 84 | UILabel *pageNumber = [[UILabel alloc]init]; 85 | pageNumber.textColor = [UIColor whiteColor]; 86 | pageNumber.textAlignment = NSTextAlignmentCenter; 87 | pageNumber.font = [UIFont systemFontOfSize:12.0f]; 88 | pageNumber.autoresizingMask = UIViewAutoresizingFlexibleWidth; 89 | [self.view addSubview:_pageNumber = pageNumber]; 90 | } 91 | return _pageNumber; 92 | } 93 | 94 | - (void)showImgBrower{ 95 | [self setingScrollViewView]; 96 | self.pageNumber.text = [self getPabeNumberText]; 97 | } 98 | 99 | - (void)addGestureRecognizers{//添加单击返回,双击缩放手势 100 | UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapAction:)]; 101 | [self.view addGestureRecognizer:tap1]; 102 | 103 | UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapAction:)]; 104 | [tap2 setNumberOfTapsRequired:2]; 105 | [self.view addGestureRecognizer:tap2]; 106 | 107 | [tap1 requireGestureRecognizerToFail:tap2];//单击优先级滞后于双击 108 | } 109 | 110 | - (void)setingScrollViewView{ 111 | /* 提示:当图片数大于等于2张时,目前设计为2个ImgScroll进行重用,修改需谨慎 */ 112 | for (int i = 0; i < MIN(2, self.imgInfos.count); i++) { 113 | YTImgScroll *imgScroll = [[YTImgScroll alloc]initWithFrame:self.scrollView.bounds]; 114 | imgScroll.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 115 | [self.scrollView addSubview:imgScroll]; 116 | [self.imgScrolls addObject:imgScroll]; 117 | } 118 | [self imgScrollToIndex:beginIndex]; 119 | } 120 | 121 | - (NSString*)getPabeNumberText{ 122 | return [NSString stringWithFormat:@"%i/%i",(int)(currentIndex+1),(int)self.imgInfos.count]; 123 | } 124 | 125 | 126 | - (void)tapAction:(UITapGestureRecognizer *)ges{ 127 | if (ges.numberOfTapsRequired == 1) { // 单击 128 | [self dismissViewControllerAnimated:NO completion:nil]; 129 | }else { //双击 130 | [currentScroll doubleTapAction]; 131 | } 132 | } 133 | 134 | #pragma mark - layout Sub Views (横竖屏切换重新布局) 135 | - (void)scrollViewLayoutSubViews{//重新布局scrollView内部的控件 136 | for (YTImgScroll *imgScroll in self.imgScrolls) { 137 | if (![imgScroll isKindOfClass:[YTImgScroll class]]) return; 138 | CGRect frame = self.scrollView.bounds; 139 | frame.origin.x = (imgScroll.imgInfo.index) * (frame.size.width); 140 | imgScroll.frame = frame; 141 | [imgScroll layoutSubview]; 142 | } 143 | 144 | [currentScroll replyStatuseAnimated:YES]; 145 | [self.scrollView setContentOffset:currentScroll.frame.origin]; 146 | } 147 | 148 | - (void)viewWillLayoutSubviews{ 149 | [super viewWillLayoutSubviews]; 150 | 151 | CGSize size = self.scrollView.bounds.size; 152 | size.width *= self.imgInfos.count; 153 | self.scrollView.contentSize = size; 154 | 155 | CGRect frame = self.view.bounds; 156 | frame.origin.y = (frame.size.height*Page_Scale) - Page_Lab_H; 157 | frame.size.height = Page_Lab_H; 158 | self.pageNumber.frame = frame; 159 | 160 | [self scrollViewLayoutSubViews]; 161 | } 162 | 163 | 164 | #pragma mark - Scroll View Deledate (scroll 代理) 165 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 166 | if (!scrollView.isDragging) return; 167 | [self scrollViewDragging:scrollView]; 168 | } 169 | 170 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 171 | 172 | [self scrollViewDragging:scrollView]; 173 | 174 | for (YTImgScroll *imgScroll in self.imgScrolls) { 175 | if (imgScroll != currentScroll) { 176 | [imgScroll replyStatuseAnimated:NO]; 177 | } 178 | } 179 | } 180 | 181 | #pragma mark - Image Scroll Reuse (imageScroll复用) 182 | - (void)scrollViewDragging:(UIScrollView *)scrollView{ 183 | CGFloat pointx = (scrollView.contentOffset.x)/(scrollView.bounds.size.width); 184 | 185 | if (scrollView.contentOffset.x > frontPointX) { 186 | [self imgScrollToIndex:ceilf(pointx)];//取上整 187 | }else{ 188 | [self imgScrollToIndex:floorf(pointx)];//取下整 189 | } 190 | 191 | NSInteger integer = pointx+0.5; 192 | if (integer != currentIndex) { 193 | //currentIndex = integer; 194 | self.pageNumber.text = [self getPabeNumberText]; 195 | } 196 | frontPointX = scrollView.contentOffset.x; 197 | } 198 | 199 | - (void)imgScrollToIndex:(NSInteger)index{ 200 | BOOL back = currentIndex > index; 201 | if ((currentIndex == index)||(index >= self.imgInfos.count)) { 202 | return; 203 | } 204 | currentIndex = index; 205 | YTImgScroll *imgScroll = [self getFollowScroll]; 206 | currentScroll = imgScroll; 207 | if (imgScroll.tag == index) return; 208 | 209 | [currentScroll replyStatuseAnimated:NO]; 210 | CGRect frame = self.scrollView.bounds; 211 | frame.origin.x = index*(frame.size.width); 212 | imgScroll.frame = frame; 213 | imgScroll.tag = index; 214 | imgScroll.imgInfo = self.imgInfos[index]; 215 | if (back) { //预加载 216 | if (currentIndex > 0) { 217 | [(YTImgInfo *)self.imgInfos[index-1] httpRequest]; 218 | } 219 | }else{ 220 | if (currentIndex < self.imgInfos.count-1) { 221 | [(YTImgInfo *)self.imgInfos[index+1] httpRequest]; 222 | } 223 | } 224 | } 225 | 226 | - (YTImgScroll *)getFollowScroll{ 227 | YTImgScroll *imgScroll = self.imgScrolls.lastObject; 228 | [self.imgScrolls removeLastObject]; 229 | [self.imgScrolls insertObject:imgScroll atIndex:0]; 230 | return imgScroll; 231 | } 232 | 233 | - (void)initEnd{ 234 | if (self.delegate && [self.delegate respondsToSelector:@selector(imgBrowerControllerInitEnd:)]) { 235 | YTImgInfo *imgInfo = self.imgInfos[beginIndex]; 236 | [self.delegate imgBrowerControllerInitEnd:imgInfo]; 237 | } 238 | } 239 | 240 | - (void)delegateWillDismiss{ 241 | if (self.delegate && [self.delegate respondsToSelector:@selector(imgBrowerControllerWillDismiss:)]) { 242 | [self.delegate imgBrowerControllerWillDismiss:currentScroll.imgInfo]; 243 | } 244 | } 245 | 246 | @end 247 | -------------------------------------------------------------------------------- /YTChatDemo/ImgBrower/YTImgInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTImgInfo.h 3 | // YTImgBrower 4 | // 5 | // Created by TI on 15/10/26. 6 | // Copyright © 2015年 ycctime.com. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | typedef NS_ENUM(NSInteger, YTImgInfoFromeType) { 12 | YTImgInfoFromeTypePhoto = 0, //来之本地相册 13 | YTImgInfoFromeTypeRemote //来之远程网络 14 | }; 15 | 16 | @interface YTImgInfo : NSObject 17 | /** image, url 有可能为空 引用时要判断 */ 18 | @property(nonatomic, strong) UIImage *image; //图片 19 | @property(nonatomic, strong, readonly) NSString *explin; //图片信息 20 | @property(nonatomic, strong, readonly) NSURL *url; //图片地址 21 | @property(nonatomic, assign, readonly) NSInteger index; //位置索引 22 | @property(nonatomic, assign, readonly) CGSize size; //图片适配后大小 23 | @property(nonatomic, assign, readonly) YTImgInfoFromeType fromeType; //来之哪里 24 | @property (nonatomic, assign, getter=ishttp) BOOL http; //网络请求成功与否 25 | 26 | + (NSArray *)imgInfosWithImgs:(NSArray *)imgs urls:(NSArray *)urls type:(YTImgInfoFromeType)type; 27 | 28 | - (void)httpRequest; // 网络请求数据 29 | @end 30 | -------------------------------------------------------------------------------- /YTChatDemo/ImgBrower/YTImgInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTImgInfo.m 3 | // YTImgBrower 4 | // 5 | // Created by TI on 15/10/26. 6 | // Copyright © 2015年 ycctime.com. All rights reserved. 7 | // 8 | 9 | @import AssetsLibrary; 10 | #import 11 | #import "YTNetworkLoad.h" 12 | #import "YTImgInfo.h" 13 | 14 | #define Max_Count_Obj 5000 //它具体是多少随便你 15 | #define Device_Size ([UIScreen mainScreen].bounds.size) 16 | 17 | @interface YTImgInfo(){ 18 | YTNetworkLoad *netLoad; 19 | } 20 | @property(nonatomic, strong) NSString *explin;//图片信息 21 | @property(nonatomic, strong) NSURL *url; //图片地址 22 | @property(nonatomic, assign) NSInteger index; //位置索引 23 | @property(nonatomic, assign) CGSize size; //图片适配后大小 24 | @property(nonatomic, assign) YTImgInfoFromeType fromeType; 25 | @end 26 | 27 | @implementation YTImgInfo 28 | + (NSArray *)imgInfosWithImgs:(NSArray *)imgs urls:(NSArray *)urls type:(YTImgInfoFromeType)type{ 29 | //根据图片及图片网址来创建一组该对象 最多为(Max_Count_Obj)个 30 | NSInteger max = MAX(imgs.count, urls.count); 31 | NSInteger maxInt = MIN(Max_Count_Obj, max); 32 | NSMutableArray *imgInfos = [NSMutableArray arrayWithCapacity:maxInt]; 33 | for (int index = 0; index < maxInt; index++) { 34 | YTImgInfo *imgInfo = [[YTImgInfo alloc]init]; 35 | 36 | id objUrl = index < urls.count?urls[index]:nil; 37 | //url 38 | switch (type) { 39 | case YTImgInfoFromeTypePhoto: 40 | { 41 | if (objUrl) [imgInfo setingPhotoWith:objUrl]; 42 | break; 43 | } 44 | case YTImgInfoFromeTypeRemote: 45 | { 46 | //图片 47 | UIImage *image = index < imgs.count?imgs[index]:nil; 48 | if (!image||(image.size.width < 1.0f)) { 49 | image = [UIImage imageNamed:@"default_img"]; 50 | } 51 | imgInfo.image = image; 52 | if (objUrl) [imgInfo setingRemoteWith:objUrl]; 53 | break; 54 | } 55 | default: 56 | break; 57 | } 58 | imgInfo.index = index; 59 | imgInfo.fromeType = type; 60 | [imgInfos addObject:imgInfo]; 61 | } 62 | return imgInfos; 63 | } 64 | 65 | 66 | - (void)setingPhotoWith:(id)objUrl{ 67 | if ([objUrl isKindOfClass:[ALAsset class]]) { 68 | UIImage *image = [UIImage imageWithCGImage:[[objUrl defaultRepresentation] fullScreenImage]]; 69 | self.image = image; 70 | self.url = [objUrl defaultRepresentation].url; 71 | self.http = YES; 72 | }else if([objUrl isKindOfClass:[NSURL class]]){ 73 | ALAssetsLibrary *libary = [[ALAssetsLibrary alloc]init]; 74 | [libary assetForURL:objUrl resultBlock:^(ALAsset *asset) { 75 | UIImage *image = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]]; 76 | self.image = image; 77 | self.url = objUrl; 78 | self.http = YES; 79 | } failureBlock:^(NSError *error) { 80 | self.url = objUrl; 81 | self.http = NO; 82 | }]; 83 | }else{ 84 | self.url = nil; 85 | self.image = [UIImage imageNamed:@"default_img"]; 86 | } 87 | } 88 | 89 | - (void)setingRemoteWith:(id)objUrl{ 90 | if ([objUrl isKindOfClass:[NSURL class]]) { 91 | self.url = objUrl; 92 | }else if([objUrl isKindOfClass:[NSString class]]){ 93 | self.url = [NSURL URLWithString:objUrl]; 94 | }else{ 95 | self.url = nil; 96 | } 97 | self.http = NO; 98 | } 99 | 100 | 101 | #pragma mark - Network loag (图片网络请求) 102 | - (void)httpRequest{ 103 | if ((self.url)&&(!self.ishttp)) { 104 | switch (self.fromeType) { 105 | case YTImgInfoFromeTypePhoto: 106 | [self photoRequest]; 107 | break; 108 | case YTImgInfoFromeTypeRemote: 109 | [self remoteRequest]; 110 | break; 111 | default: 112 | break; 113 | } 114 | } 115 | } 116 | 117 | - (void)photoRequest{ 118 | ALAssetsLibrary *libary = [[ALAssetsLibrary alloc]init]; 119 | __weak __typeof (self)weekSelf = self; 120 | [libary assetForURL:self.url resultBlock:^(ALAsset *asset) { 121 | UIImage *image = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]]; 122 | __strong __typeof (weekSelf)strongSelf = weekSelf; 123 | strongSelf.image = image; 124 | strongSelf.http = YES; 125 | } failureBlock:^(NSError *error) { 126 | 127 | }]; 128 | } 129 | 130 | - (void)remoteRequest{ 131 | __weak __typeof (self)weekSelf = self; 132 | [netLoad cancel]; 133 | netLoad = [[YTNetworkLoad alloc]initWithUrl:self.url success:^(NSURLRequest *request, id data) { 134 | __strong __typeof (weekSelf)strongSelf = weekSelf; 135 | strongSelf.image = [UIImage imageWithData:data]; 136 | strongSelf.http = YES; 137 | netLoad = nil; 138 | } failure:^(NSError *error) { 139 | netLoad = nil; 140 | }]; 141 | } 142 | 143 | - (void)setImage:(UIImage *)image{ 144 | if (image) { 145 | _image = image; 146 | self.size = [self imageSize]; 147 | } 148 | } 149 | 150 | - (CGSize)imageSize{//图片根据屏幕大小来调整size,保证与屏幕比例适配 151 | CGFloat wid = _image.size.width; 152 | CGFloat heig = _image.size.height; 153 | if ((wid <= Device_Size.width) && (heig <= Device_Size.height)) { 154 | return _image.size; 155 | } 156 | 157 | CGFloat scale_poor = (wid/Device_Size.width)-( heig/Device_Size.height); 158 | CGSize endSize = CGSizeZero; 159 | 160 | if (scale_poor > 0) { 161 | CGFloat height_now = heig*(Device_Size.width/wid); 162 | endSize = CGSizeMake(Device_Size.width, height_now); 163 | }else{ 164 | CGFloat width_now = wid*(Device_Size.height/heig); 165 | endSize = CGSizeMake(width_now, Device_Size.height); 166 | } 167 | return endSize; 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /YTChatDemo/ImgBrower/YTImgScroll.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTImgScroll.h 3 | // YTImgBrower 4 | // 5 | // Created by TI on 15/10/26. 6 | // Copyright © 2015年 ycctime.com. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YTImgInfo.h" 11 | 12 | @interface YTImgScroll : UIScrollView 13 | @property(nonatomic, strong) YTImgInfo *imgInfo; 14 | /**恢复图片原始状态 animated->是否需要动画*/ 15 | - (void)replyStatuseAnimated:(BOOL)animated; 16 | 17 | /**双击事件(父视触发,放大或缩小)*/ 18 | - (void)doubleTapAction; 19 | 20 | /**在横竖屏变化是调用 重新布局*/ 21 | - (void)layoutSubview; 22 | @end 23 | -------------------------------------------------------------------------------- /YTChatDemo/ImgBrower/YTImgScroll.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTImgScroll.m 3 | // YTImgBrower 4 | // 5 | // Created by TI on 15/10/26. 6 | // Copyright © 2015年 ycctime.com. All rights reserved. 7 | // 8 | 9 | @import AssetsLibrary; 10 | #import "YTImgScroll.h" 11 | #import "MBProgressHUD.h" 12 | #import "YTNetworkLoad.h" 13 | 14 | #define MaxZoomScale 2.5f 15 | #define MinZoomScale 1.0f 16 | 17 | @interface YTImgScroll(){ 18 | MBProgressHUD *HUD; 19 | YTNetworkLoad *netLoad; 20 | BOOL needAnimal; 21 | } 22 | @property (nonatomic, strong) UIImageView *imgView; 23 | @end 24 | 25 | @implementation YTImgScroll 26 | 27 | - (UIImageView *)imgView{ 28 | if (!_imgView) { 29 | UIImageView *imgView = [[UIImageView alloc]init]; 30 | imgView.userInteractionEnabled = YES; 31 | [imgView setContentMode:UIViewContentModeScaleAspectFit]; 32 | [imgView setClipsToBounds:YES]; 33 | [self addSubview:_imgView = imgView]; 34 | // 添加HUD和手势 35 | [self addHUD]; 36 | [self addGestureRecognizers]; 37 | } 38 | return _imgView; 39 | } 40 | 41 | - (instancetype)initWithFrame:(CGRect)frame{ 42 | if (self = [super initWithFrame:frame]) { 43 | self.delegate = self; 44 | self.alwaysBounceVertical = NO; 45 | self.contentSize = self.bounds.size; 46 | self.maximumZoomScale = MaxZoomScale; 47 | self.minimumZoomScale = MinZoomScale; 48 | self.showsVerticalScrollIndicator = NO; 49 | self.showsHorizontalScrollIndicator = NO; 50 | self.tag = -1; /* 注明:后面以tag来判断当前页以免造成混乱 */ 51 | } 52 | return self; 53 | } 54 | 55 | - (void)addGestureRecognizers{ 56 | //长按手势 保存图片 57 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressAction:)]; 58 | [self addGestureRecognizer:longPress]; 59 | } 60 | 61 | - (void)addHUD{ 62 | HUD = [[MBProgressHUD alloc]initWithView:self]; 63 | HUD.mode = MBProgressHUDModeIndeterminate; 64 | HUD.color = [UIColor clearColor]; 65 | HUD.delegate = self; 66 | [self addSubview:HUD]; 67 | } 68 | 69 | #pragma mark 横竖屏切换布局 70 | - (void)layoutSubview{ 71 | [_imgInfo setImage:_imgInfo.image]; 72 | [self setImgInfo:_imgInfo]; 73 | } 74 | 75 | -(void)setImgInfo:(YTImgInfo *)imgInfo{ 76 | if (!imgInfo) return; 77 | BOOL BUG = (_imgInfo.index == imgInfo.index); 78 | _imgInfo = imgInfo; 79 | self.imgView.image = imgInfo.image; 80 | CGRect frame = self.bounds; 81 | frame.size = imgInfo.size; 82 | if ((needAnimal == YES) && (BUG == YES)) { 83 | needAnimal = NO; 84 | [UIView animateWithDuration:0.2 animations:^{ 85 | self.imgView.frame = frame; 86 | [self scrollViewDidZoom:self]; 87 | }]; 88 | }else{ 89 | needAnimal = NO; 90 | self.imgView.frame = frame; 91 | [self scrollViewDidZoom:self]; 92 | [self loadHttpImg]; 93 | } 94 | } 95 | 96 | #pragma mark - Network loag (图片网络请求) 97 | - (void)loadHttpImg{ 98 | //自己写的网络请求加载 99 | if ((_imgInfo.url)&&(!_imgInfo.ishttp)) { 100 | [HUD show:YES]; 101 | __weak __typeof (self)weekSelf = self; 102 | switch (_imgInfo.fromeType) { 103 | case YTImgInfoFromeTypePhoto: 104 | { 105 | ALAssetsLibrary *libary = [[ALAssetsLibrary alloc]init]; 106 | [libary assetForURL:_imgInfo.url resultBlock:^(ALAsset *asset) { 107 | UIImage *image = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]]; 108 | __strong __typeof (weekSelf)strongSelf = weekSelf; 109 | [strongSelf requestResult:image]; 110 | } failureBlock:^(NSError *error) { 111 | __strong __typeof (weekSelf)strongSelf = weekSelf; 112 | [strongSelf requestResult:nil]; 113 | }]; 114 | } 115 | break; 116 | case YTImgInfoFromeTypeRemote: 117 | { 118 | [netLoad cancel]; 119 | netLoad = [[YTNetworkLoad alloc]initWithUrl:_imgInfo.url success:^(NSURLRequest *request, id data) { 120 | __strong __typeof (weekSelf)strongSelf = weekSelf; 121 | [strongSelf requestResult:[UIImage imageWithData:data]]; 122 | } failure:^(NSError *error) { 123 | __strong __typeof (weekSelf)strongSelf = weekSelf; 124 | [strongSelf requestResult:nil]; 125 | }]; 126 | 127 | netLoad.updataBlock = ^(float progress){ 128 | __strong __typeof (weekSelf)strongSelf = weekSelf; 129 | strongSelf->HUD.mode = MBProgressHUDModeDeterminate; 130 | strongSelf->HUD.progress = progress; 131 | }; 132 | } 133 | default: 134 | break; 135 | } 136 | }else{ 137 | [HUD hide:YES]; 138 | } 139 | } 140 | 141 | - (void)requestResult:(UIImage*)image{ 142 | if (HUD.isHidden) return; 143 | if (image) { 144 | needAnimal = YES; 145 | [_imgInfo setImage:image]; 146 | _imgInfo.http = YES; 147 | [self setImgInfo:_imgInfo]; 148 | } 149 | [HUD hide:YES]; 150 | } 151 | 152 | 153 | #pragma mark - Public Action 154 | #pragma mark 恢复图片原始状态 155 | - (void)replyStatuseAnimated:(BOOL)animated{ 156 | [self setZoomScale:1.0f animated:animated]; 157 | } 158 | 159 | #pragma mark 双击事件 160 | - (void)doubleTapAction{//图片变大或变小 161 | if ((self.minimumZoomScale<=self.zoomScale)&&(self.maximumZoomScale>self.zoomScale)) { 162 | [self setZoomScale:self.maximumZoomScale animated:YES]; 163 | }else { 164 | [self setZoomScale:self.minimumZoomScale animated:YES]; 165 | } 166 | } 167 | 168 | #pragma mark - Scroll View Deledate (不断适配图片大小) 169 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{ 170 | return _imgView; 171 | } 172 | 173 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView{ 174 | //放大或缩小时图片位置(frame)调整,保证居中 175 | CGFloat Wo = self.frame.size.width - self.contentInset.left - self.contentInset.right; 176 | CGFloat Ho = self.frame.size.height - self.contentInset.top - self.contentInset.bottom; 177 | UIImageView *imgView = _imgView; 178 | CGFloat W = imgView.frame.size.width; 179 | CGFloat H = imgView.frame.size.height; 180 | CGRect rct = imgView.frame; 181 | rct.origin.x = MAX((Wo-W)*0.5, 0); 182 | rct.origin.y = MAX((Ho-H)*0.5, 0); 183 | imgView.frame = rct; 184 | } 185 | 186 | #pragma mark - Image Save (图像保存) 187 | #pragma mark 长按手势 188 | -(void)longPressAction:(UILongPressGestureRecognizer*)longPress{ 189 | if (longPress.state == UIGestureRecognizerStateBegan) { 190 | if (YES) {//[YTDeviceTest userAuthorizationPhotoStatus] 191 | UIActionSheet *actionSheet = [[UIActionSheet alloc]initWithTitle:nil delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@"保存到手机", nil]; 192 | [actionSheet showInView:self]; 193 | } 194 | } 195 | } 196 | 197 | #pragma mark Action Sheet Delegate (actionSheet代理) 198 | - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex{ 199 | if (buttonIndex == 0) { 200 | if (self.imgView.image) {//保存图片 201 | UIImageWriteToSavedPhotosAlbum(self.imgView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); 202 | } 203 | } 204 | } 205 | 206 | #pragma mark 图片保存结果回调 207 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{//保存图片回调 208 | if (error != NULL){ //失败 209 | NSLog(@"图片保存失败->%@",error); 210 | }else{//成功 211 | NSLog(@"图片保存成功"); 212 | } 213 | } 214 | 215 | #pragma mark - HUD delegate 216 | - (void)hudWasHidden:(MBProgressHUD *)hud{ 217 | HUD.progress = 0.0f; 218 | } 219 | 220 | @end 221 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/EmojiFile.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Id 7 | local 8 | path 9 | EmojiSutra 10 | type 11 | 0 12 | disable 13 | 14 | 15 | 16 | Id 17 | local 18 | path 19 | EmojiPanda 20 | type 21 | 0 22 | disable 23 | 24 | 25 | 26 | Id 27 | local 28 | path 29 | EmojiGirl 30 | type 31 | 1 32 | disable 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/EmojiGirl.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | image 6 | girl_beg 7 | name 8 | 矮矮女 9 | Id 10 | local 11 | icons 12 | 13 | 14 | emojiName 15 | 求包养 16 | emojiImage 17 | girl_beg 18 | emojiCode 19 | 20 | 21 | 22 | emojiName 23 | 求包养 24 | emojiImage 25 | girl_beg 26 | emojiCode 27 | 28 | 29 | 30 | emojiName 31 | 求包养 32 | emojiImage 33 | girl_beg 34 | emojiCode 35 | 36 | 37 | 38 | emojiName 39 | 求包养 40 | emojiImage 41 | girl_beg 42 | emojiCode 43 | 44 | 45 | 46 | emojiName 47 | 求包养 48 | emojiImage 49 | girl_beg 50 | emojiCode 51 | 52 | 53 | 54 | emojiName 55 | 求包养 56 | emojiImage 57 | girl_beg 58 | emojiCode 59 | 60 | 61 | 62 | emojiName 63 | 求包养 64 | emojiImage 65 | girl_beg 66 | emojiCode 67 | 68 | 69 | 70 | emojiName 71 | 求包养 72 | emojiImage 73 | girl_beg 74 | emojiCode 75 | 76 | 77 | 78 | emojiName 79 | 求包养 80 | emojiImage 81 | girl_beg 82 | emojiCode 83 | 84 | 85 | 86 | emojiName 87 | 求包养 88 | emojiImage 89 | girl_beg 90 | emojiCode 91 | 92 | 93 | 94 | emojiName 95 | 求包养 96 | emojiImage 97 | girl_beg 98 | emojiCode 99 | 100 | 101 | 102 | emojiName 103 | 求包养 104 | emojiImage 105 | girl_beg 106 | emojiCode 107 | 108 | 109 | 110 | emojiName 111 | 求包养 112 | emojiImage 113 | girl_beg 114 | emojiCode 115 | 116 | 117 | 118 | emojiName 119 | 求包养 120 | emojiImage 121 | girl_beg 122 | emojiCode 123 | 124 | 125 | 126 | emojiName 127 | 求包养 128 | emojiImage 129 | girl_beg 130 | emojiCode 131 | 132 | 133 | 134 | emojiName 135 | 求包养 136 | emojiImage 137 | girl_beg 138 | emojiCode 139 | 140 | 141 | 142 | emojiName 143 | 求包养 144 | emojiImage 145 | girl_beg 146 | emojiCode 147 | 148 | 149 | 150 | emojiName 151 | 求包养 152 | emojiImage 153 | girl_beg 154 | emojiCode 155 | 156 | 157 | 158 | emojiName 159 | 求包养 160 | emojiImage 161 | girl_beg 162 | emojiCode 163 | 164 | 165 | 166 | emojiName 167 | 求包养 168 | emojiImage 169 | girl_beg 170 | emojiCode 171 | 172 | 173 | 174 | emojiName 175 | 求包养 176 | emojiImage 177 | girl_beg 178 | emojiCode 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/EmojiPanda.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | image 6 | smile 7 | name 8 | 熊猫 9 | Id 10 | local 11 | icons 12 | 13 | 14 | emojiName 15 | 微笑 16 | emojiImage 17 | smile 18 | emojiCode 19 | [smile] 20 | 21 | 22 | emojiName 23 | 微笑 24 | emojiImage 25 | smile 26 | emojiCode 27 | [smile] 28 | 29 | 30 | emojiName 31 | 微笑 32 | emojiImage 33 | smile 34 | emojiCode 35 | [smile] 36 | 37 | 38 | emojiName 39 | 微笑 40 | emojiImage 41 | smile 42 | emojiCode 43 | [smile] 44 | 45 | 46 | emojiName 47 | 微笑 48 | emojiImage 49 | smile 50 | emojiCode 51 | [smile] 52 | 53 | 54 | emojiName 55 | 微笑 56 | emojiImage 57 | smile 58 | emojiCode 59 | [smile] 60 | 61 | 62 | emojiName 63 | 微笑 64 | emojiImage 65 | smile 66 | emojiCode 67 | [smile] 68 | 69 | 70 | emojiName 71 | 微笑 72 | emojiImage 73 | smile 74 | emojiCode 75 | [smile] 76 | 77 | 78 | emojiName 79 | 微笑 80 | emojiImage 81 | smile 82 | emojiCode 83 | [smile] 84 | 85 | 86 | emojiName 87 | 微笑 88 | emojiImage 89 | smile 90 | emojiCode 91 | [smile] 92 | 93 | 94 | emojiName 95 | 微笑 96 | emojiImage 97 | smile 98 | emojiCode 99 | [smile] 100 | 101 | 102 | emojiName 103 | 微笑 104 | emojiImage 105 | smile 106 | emojiCode 107 | [smile] 108 | 109 | 110 | emojiName 111 | 微笑 112 | emojiImage 113 | smile 114 | emojiCode 115 | [smile] 116 | 117 | 118 | emojiName 119 | 微笑 120 | emojiImage 121 | smile 122 | emojiCode 123 | [smile] 124 | 125 | 126 | emojiName 127 | 微笑 128 | emojiImage 129 | smile 130 | emojiCode 131 | [smile] 132 | 133 | 134 | emojiName 135 | 微笑 136 | emojiImage 137 | smile 138 | emojiCode 139 | [smile] 140 | 141 | 142 | emojiName 143 | 微笑 144 | emojiImage 145 | smile 146 | emojiCode 147 | [smile] 148 | 149 | 150 | emojiName 151 | 微笑 152 | emojiImage 153 | smile 154 | emojiCode 155 | [smile] 156 | 157 | 158 | emojiName 159 | 微笑 160 | emojiImage 161 | smile 162 | emojiCode 163 | [smile] 164 | 165 | 166 | emojiName 167 | 微笑 168 | emojiImage 169 | smile 170 | emojiCode 171 | [smile] 172 | 173 | 174 | emojiName 175 | 微笑 176 | emojiImage 177 | smile 178 | emojiCode 179 | [smile] 180 | 181 | 182 | emojiName 183 | 微笑 184 | emojiImage 185 | smile 186 | emojiCode 187 | [smile] 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/EmojiSutra.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | image 6 | grinning 7 | name 8 | 经典 9 | Id 10 | local 11 | icons 12 | 13 | 14 | emojiName 15 | 哈哈 16 | emojiImage 17 | grinning 18 | emojiCode 19 | [grinning] 20 | 21 | 22 | emojiName 23 | 哈哈 24 | emojiImage 25 | grinning 26 | emojiCode 27 | [grinning] 28 | 29 | 30 | emojiName 31 | 哈哈 32 | emojiImage 33 | grinning 34 | emojiCode 35 | [grinning] 36 | 37 | 38 | emojiName 39 | 哈哈 40 | emojiImage 41 | grinning 42 | emojiCode 43 | [grinning] 44 | 45 | 46 | emojiName 47 | 哈哈 48 | emojiImage 49 | grinning 50 | emojiCode 51 | [grinning] 52 | 53 | 54 | emojiName 55 | 哈哈 56 | emojiImage 57 | grinning 58 | emojiCode 59 | [grinning] 60 | 61 | 62 | emojiName 63 | 哈哈 64 | emojiImage 65 | grinning 66 | emojiCode 67 | [grinning] 68 | 69 | 70 | emojiName 71 | 哈哈 72 | emojiImage 73 | grinning 74 | emojiCode 75 | [grinning] 76 | 77 | 78 | emojiName 79 | 哈哈 80 | emojiImage 81 | grinning 82 | emojiCode 83 | [grinning] 84 | 85 | 86 | emojiName 87 | 哈哈 88 | emojiImage 89 | grinning 90 | emojiCode 91 | [grinning] 92 | 93 | 94 | emojiName 95 | 哈哈 96 | emojiImage 97 | grinning 98 | emojiCode 99 | [grinning] 100 | 101 | 102 | emojiName 103 | 哈哈 104 | emojiImage 105 | grinning 106 | emojiCode 107 | [grinning] 108 | 109 | 110 | emojiName 111 | 哈哈 112 | emojiImage 113 | grinning 114 | emojiCode 115 | [grinning] 116 | 117 | 118 | emojiName 119 | 哈哈 120 | emojiImage 121 | grinning 122 | emojiCode 123 | [grinning] 124 | 125 | 126 | emojiName 127 | 哈哈 128 | emojiImage 129 | grinning 130 | emojiCode 131 | [grinning] 132 | 133 | 134 | emojiName 135 | 哈哈 136 | emojiImage 137 | grinning 138 | emojiCode 139 | [grinning] 140 | 141 | 142 | emojiName 143 | 哈哈 144 | emojiImage 145 | grinning 146 | emojiCode 147 | [grinning] 148 | 149 | 150 | emojiName 151 | 哈哈 152 | emojiImage 153 | grinning 154 | emojiCode 155 | [grinning] 156 | 157 | 158 | emojiName 159 | 哈哈 160 | emojiImage 161 | grinning 162 | emojiCode 163 | [grinning] 164 | 165 | 166 | emojiName 167 | 哈哈 168 | emojiImage 169 | grinning 170 | emojiCode 171 | [grinning] 172 | 173 | 174 | emojiName 175 | 哈哈 176 | emojiImage 177 | grinning 178 | emojiCode 179 | [grinning] 180 | 181 | 182 | emojiName 183 | 哈哈 184 | emojiImage 185 | grinning 186 | emojiCode 187 | [grinning] 188 | 189 | 190 | emojiName 191 | 哈哈 192 | emojiImage 193 | grinning 194 | emojiCode 195 | [grinning] 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/girl_beg@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/girl_beg@2x.gif -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/grinning@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/grinning@2x.png -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/smile@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/KeyBoard/Emoji/EmojiModel/Resource/smile@2x.png -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmoji.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmoji.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface YTEmoji : NSObject 12 | 13 | @property (nonatomic, strong) NSString *emojiName; //名字(中文) 14 | @property (nonatomic, strong) NSString *emojiImage; //图片名字 15 | @property (nonatomic, strong) NSString *emojiCode; //表情码(要和“某卓”沟通) 16 | @end 17 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmoji.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmoji.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmoji.h" 10 | 11 | @implementation YTEmoji 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiChartletM.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiChartletM.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiM.h" 10 | 11 | @interface YTEmojiChartletM : YTEmojiM 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiChartletM.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiChartletM.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiChartletM.h" 10 | 11 | @implementation YTEmojiChartletM 12 | 13 | -(void)setNorms:(YTEmojiNorms)norms{ 14 | norms.lines = 2; 15 | norms.boardWH = 54.5f; 16 | norms.spaceBoard = 12.0f; 17 | norms.spaceHorizontalMIN = 20.0f; 18 | norms.spaceVerticalityMIN = 15.0f; 19 | [super setNorms:norms]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiFile.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YTEmojiChartletM.h" 11 | #import "YTEmojiIconM.h" 12 | 13 | @interface YTEmojiFile : NSObject 14 | 15 | /* 包含YTEmojiIconM子类对象 */ 16 | + (NSArray *)emojiModelS; 17 | @end 18 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiFile.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiFile.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiFile.h" 10 | #import "NSDictionary+YTSafe.h" 11 | 12 | @interface YTEmojiFileM : NSObject 13 | 14 | @property (nonatomic, strong) NSString * path; //表情路径 15 | @property (nonatomic, strong) NSString * Id; //标示(来自网络还是本地) 16 | @property (nonatomic, assign) YTEmojiType type; //表情类型 17 | @property (nonatomic, assign, getter=isDisable) BOOL disable; //判断可用 18 | @end 19 | 20 | @implementation YTEmojiFileM 21 | @end 22 | 23 | @implementation YTEmojiFile 24 | 25 | + (NSArray *)emojiModelS{ 26 | return [self emojiDataSource]; 27 | } 28 | 29 | + (NSArray *)emojiDataSource{ 30 | NSMutableArray *emojiMS = [NSMutableArray array]; 31 | NSArray *emojiFS = [[self class] emojiFiles]; 32 | for (YTEmojiFileM *ef in emojiFS) { 33 | NSString *emojiPath = [[NSBundle mainBundle]pathForResource:ef.path ofType:@"plist"]; 34 | NSDictionary *emojiDic = [NSDictionary dictionaryWithContentsOfFile:emojiPath]; 35 | YTEmojiM *em = nil; 36 | switch (ef.type) { 37 | case YTEmojiTypeIcon: 38 | em = [[YTEmojiIconM alloc]initWithDic:emojiDic]; 39 | break; 40 | case YTEmojiTypeChartlet: 41 | em = [[YTEmojiChartletM alloc]initWithDic:emojiDic]; 42 | break; 43 | default: 44 | break; 45 | } 46 | if (em) { 47 | em.type = ef.type; 48 | [emojiMS addObject:em]; 49 | } 50 | } 51 | 52 | return [emojiMS copy]; 53 | } 54 | 55 | + (NSArray *)emojiFiles{ 56 | NSMutableArray *emojiFS = [NSMutableArray array]; 57 | NSString *filePath = [[NSBundle mainBundle]pathForResource:@"EmojiFile" ofType:@"plist"]; 58 | 59 | 60 | NSArray *files = [NSArray arrayWithContentsOfFile:filePath]; 61 | for (NSDictionary *dic in files) { 62 | YTEmojiFileM *ef = [[YTEmojiFileM alloc]init]; 63 | ef.path = [dic safeStringValueForKey:@"path"]; 64 | ef.Id = [dic safeStringValueForKey:@"Id"]; 65 | ef.disable = [dic safeBoolValueForKey:@"disable"]; 66 | ef.type = (YTEmojiType)[dic safeIntegerValueForKey:@"type"]; 67 | if ((!ef.isDisable) && (ef.path.length > 0)) { 68 | [emojiFS addObject:ef]; 69 | } 70 | } 71 | 72 | return emojiFS; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiIconM.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiIconM.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiM.h" 10 | 11 | @interface YTEmojiIconM : YTEmojiM 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiIconM.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiIconM.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiIconM.h" 10 | 11 | @implementation YTEmojiIconM 12 | 13 | -(void)setNorms:(YTEmojiNorms)norms{ 14 | norms.lines = 3; 15 | norms.boardWH = 32.0f; 16 | norms.spaceBoard = 12.0f; 17 | norms.spaceHorizontalMIN = 20.0f; 18 | norms.spaceVerticalityMIN = 15.0f; 19 | [super setNorms:norms]; 20 | } 21 | 22 | -(NSInteger)countOnePage{ 23 | return ([super countOnePage] - 1);//最后一个是删除键 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiM.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiM.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YTEmoji.h" 11 | @import UIKit; 12 | 13 | #define WIDTH ([UIScreen mainScreen].bounds.size.width) 14 | 15 | #if !defined(YT_INLINE) 16 | # if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L 17 | # define YT_INLINE static inline 18 | # elif defined(__cplusplus) 19 | # define YT_INLINE static inline 20 | # elif defined(__GNUC__) 21 | # define YT_INLINE static __inline__ 22 | # else 23 | # define YT_INLINE static 24 | # endif 25 | #endif 26 | 27 | typedef NS_ENUM(NSInteger, YTEmojiType){ 28 | YTEmojiTypeIcon = 0, //一般表情图片 29 | YTEmojiTypeChartlet //贴图 30 | }; 31 | 32 | struct YTEmojiNorms{ 33 | NSUInteger lines; //行数 34 | CGFloat boardWH; //宽高 35 | CGFloat spaceBoard; //距离边框距离(水平=垂直) 36 | CGFloat spaceHorizontalMIN; //水平间距(min) 37 | CGFloat spaceVerticalityMIN; //垂直间距(min) 38 | }; 39 | 40 | typedef struct YTEmojiNorms YTEmojiNorms; 41 | 42 | YT_INLINE YTEmojiNorms YTEmojiNormsMake(NSUInteger lines, CGFloat boardWH, CGFloat spaceBoard, CGFloat spaceHorizontalMIN, CGFloat spaceVerticalityMIN); 43 | 44 | @interface YTEmojiM : NSObject 45 | 46 | @property (nonatomic, strong) NSString *name; //这一类表情总名称 47 | @property (nonatomic, strong) NSString *image; //这一类表情代表图标 48 | @property (nonatomic, assign) YTEmojiType type; //类型 49 | 50 | @property (nonatomic, strong, readonly) NSString *Id; //标示(来自网络http还是本地local) 51 | @property (nonatomic, strong, readonly) NSArray *icons; //表情集合 52 | 53 | @property (nonatomic, assign) YTEmojiNorms norms; //一般交于子类赋值 54 | 55 | /* 通过字典初始化 */ 56 | - (instancetype)initWithDic:(NSDictionary *)dic; 57 | 58 | /* 一行可显示多少个表情 */ 59 | - (NSInteger)countOneLine; 60 | 61 | /* 一页多少个表情 */ 62 | - (NSInteger)countOnePage; 63 | 64 | /* 一共多少页 */ 65 | - (NSInteger)countPageAll; 66 | 67 | /* 第index页包含多少个表情 */ 68 | - (NSInteger)countPageIndex:(NSInteger)index; 69 | @end 70 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiModel/YTEmojiM.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiM.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "NSDictionary+YTSafe.h" 10 | #import "YTEmojiM.h" 11 | 12 | NSString *const local = @"local"; 13 | NSString *const network = @"http"; 14 | 15 | YTEmojiNorms YTEmojiNormsMake(NSUInteger lines, CGFloat boardWH, CGFloat spaceBoard, CGFloat spaceHorizontalMIN, CGFloat spaceVerticalityMIN){ 16 | YTEmojiNorms norms; 17 | norms.lines = lines; 18 | norms.boardWH = boardWH; 19 | norms.spaceBoard = spaceBoard; 20 | norms.spaceHorizontalMIN = spaceHorizontalMIN; 21 | norms.spaceVerticalityMIN = spaceVerticalityMIN; 22 | return norms; 23 | } 24 | 25 | @interface YTEmojiM() 26 | 27 | @property (nonatomic, strong) NSString *Id; //标示(来自网络还是本地) 28 | @property (nonatomic, strong) NSArray *icons; //表情集合 29 | @end 30 | 31 | @implementation YTEmojiM 32 | 33 | #pragma mark - public api 34 | - (instancetype)initWithDic:(NSDictionary *)dic{ 35 | if (self = [super init]) { 36 | self.image = [dic safeStringValueForKey:@"image"]; 37 | self.name = [dic safeStringValueForKey:@"name"]; 38 | self.Id = [dic safeStringValueForKey:@"Id"]; 39 | self.icons = [dic safeArrayForKey:@"icons"]; 40 | self.norms = YTEmojiNormsMake(0, 0, 0, 0, 0); 41 | } 42 | return self; 43 | } 44 | 45 | - (NSInteger)countOneLine{ 46 | YTEmojiNorms norms = self.norms; 47 | return floorf((WIDTH-norms.spaceBoard*2+norms.spaceHorizontalMIN)/(norms.boardWH + norms.spaceHorizontalMIN)); 48 | } 49 | 50 | - (NSInteger)countOnePage{ 51 | NSInteger countLine = [self countOneLine]; 52 | return (countLine*self.norms.lines); 53 | } 54 | 55 | - (NSInteger)countPageAll{ 56 | NSInteger countPage = [self countOnePage]; 57 | return ceilf(self.icons.count/((float)countPage)); 58 | } 59 | 60 | - (NSInteger)countPageIndex:(NSInteger)index{ 61 | NSInteger countPage = [self countOnePage]; 62 | NSInteger count = countPage*(index+1); 63 | if (count <= self.icons.count) { 64 | return countPage; 65 | }else{ 66 | return (self.icons.count-(countPage*MAX(index, 0))); 67 | } 68 | } 69 | 70 | #pragma mark - self action 71 | - (void)setIcons:(NSArray *)icons{ 72 | if (!icons) { 73 | _icons = @[]; 74 | }else{ 75 | NSMutableArray *iconArray = [NSMutableArray arrayWithCapacity:icons.count]; 76 | for (NSDictionary * dic in icons) { 77 | YTEmoji *ej = [YTEmoji new]; 78 | ej.emojiName = [dic safeStringValueForKey:@"emojiName"]; 79 | ej.emojiImage = [dic safeStringValueForKey:@"emojiImage"]; 80 | ej.emojiCode = [dic safeStringValueForKey:@"emojiCode"]; 81 | [iconArray addObject:ej]; 82 | } 83 | _icons = [iconArray copy]; 84 | } 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiBottom.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiBottom.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | // 如果你不喜欢这个效果可以自己写个bottom或者对它加以修改,来吻合你的想法。 9 | 10 | #import 11 | 12 | #define EMOJI_BOTTOM_HEIGHT 35.0f 13 | #define EMOJI_BOTTOM_BUTTON_WIDTH 40.0f 14 | 15 | @interface YTEmojiBottom : UIView 16 | 17 | @property (nonatomic, strong) UIButton *rightButton; //发送 18 | @property (nonatomic, strong) UIButton *liftButton; //添加 19 | @property (nonatomic, strong) UIScrollView *scrollView; //表情栏 20 | 21 | //发送按钮-隐藏与显示 22 | - (void)rightButtonAnimationHiden:(BOOL)hiden; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiBottom.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiBottom.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "UIView+YTLayer.h" 10 | #import "YTEmojiBottom.h" 11 | 12 | @interface YTLine : UIView 13 | 14 | @end 15 | 16 | @implementation YTLine 17 | 18 | - (void)addToSuperView:(UIView *)superView{ 19 | if (superView) { 20 | CGSize size = superView.bounds.size; 21 | CGRect frame = CGRectMake(size.width-0.5f, 5.0f, 0.5f, size.height-10.0f); 22 | self.frame = frame; 23 | self.backgroundColor = [UIColor grayColor]; 24 | [superView addSubview:self]; 25 | } 26 | } 27 | 28 | @end 29 | 30 | @interface YTScrollView : UIScrollView 31 | 32 | @end 33 | 34 | @implementation YTScrollView 35 | 36 | - (instancetype)initWithFrame:(CGRect)frame{ 37 | if (self = [super initWithFrame:frame]) { 38 | self.showsHorizontalScrollIndicator = NO; 39 | self.showsVerticalScrollIndicator = NO; 40 | self.alwaysBounceVertical = NO; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)addSubview:(UIView *)view{ 46 | if (view) { 47 | YTLine *line = [[YTLine alloc]init]; 48 | [line addToSuperView:view]; 49 | [super addSubview:view]; 50 | } 51 | } 52 | 53 | - (void)setContentSize:(CGSize)contentSize{ 54 | contentSize.width = MAX(contentSize.width+(EMOJI_BOTTOM_BUTTON_WIDTH*1.5f), self.bounds.size.width+1); 55 | [super setContentSize:contentSize]; 56 | } 57 | 58 | @end 59 | 60 | @implementation YTEmojiBottom 61 | 62 | - (instancetype)initWithFrame:(CGRect)frame{ 63 | if (self = [super initWithFrame:frame]) { 64 | [self borderWithColor:[UIColor lightGrayColor] borderWidth:0.5]; 65 | [self initUI]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)initUI{ 71 | [self addScrollView]; 72 | [self addBts]; 73 | } 74 | 75 | - (void)addBts{ 76 | UIColor *viewBGColor = [UIColor whiteColor]; 77 | UIColor *btTitleColor = [UIColor grayColor]; 78 | //lift button 79 | CGRect frame = CGRectMake(0, 0, EMOJI_BOTTOM_BUTTON_WIDTH, self.frame.size.height); 80 | UIView *liftView = [[UIView alloc]initWithFrame:frame]; 81 | liftView.backgroundColor = viewBGColor; 82 | UIButton *lift = [[UIButton alloc]initWithFrame:liftView.bounds]; 83 | [lift setTitle:@"+" forState:UIControlStateNormal]; 84 | [lift setTitleColor:btTitleColor forState:UIControlStateNormal]; 85 | YTLine *line = [[YTLine alloc]init]; 86 | [line addToSuperView:lift]; 87 | [liftView addSubview:lift]; 88 | self.liftButton = lift; 89 | [self addSubview:liftView]; 90 | //right button 91 | frame.origin.x = self.bounds.size.width - EMOJI_BOTTOM_BUTTON_WIDTH*1.5; 92 | frame.size.width *= 1.5; 93 | UIView *rightView = [[UIView alloc]initWithFrame:frame]; 94 | rightView.backgroundColor = viewBGColor; 95 | frame = rightView.bounds; 96 | frame.size.width*= 0.75f; 97 | frame.size.height*= 0.75f; 98 | UIButton *right = [[UIButton alloc]initWithFrame:frame]; 99 | CGSize size = rightView.bounds.size; 100 | right.center = CGPointMake(size.width*0.5f, size.height*0.5f); 101 | [right cornerRadius:5.0f borderColor:btTitleColor borderWidth:1.0f]; 102 | right.titleLabel.font = [UIFont systemFontOfSize:13]; 103 | [right setTitle:@"发送" forState:UIControlStateNormal]; 104 | [right setTitleColor:btTitleColor forState:UIControlStateNormal]; 105 | [rightView addSubview:right]; 106 | self.rightButton = right; 107 | [self addSubview:rightView]; 108 | } 109 | 110 | - (void)addScrollView{ 111 | CGRect frame = self.bounds; 112 | frame.origin.x = EMOJI_BOTTOM_BUTTON_WIDTH; 113 | frame.size.width -= EMOJI_BOTTOM_BUTTON_WIDTH; 114 | YTScrollView *scroll = [[YTScrollView alloc]initWithFrame:frame]; 115 | [self addSubview:scroll]; 116 | self.scrollView = scroll; 117 | } 118 | 119 | - (void)rightButtonAnimationHiden:(BOOL)hiden{ 120 | CGAffineTransform Transform; 121 | CGRect frame = self.rightButton.superview.frame; 122 | if (hiden) { 123 | if (CGRectGetMinX(frame) >= self.bounds.size.width ) { 124 | return; 125 | } 126 | Transform = CGAffineTransformMakeTranslation(CGRectGetWidth(frame), 0); 127 | }else{ 128 | if (CGRectGetMinX(frame) < self.bounds.size.width ){ 129 | return; 130 | } 131 | Transform = CGAffineTransformIdentity; 132 | } 133 | [UIView animateWithDuration:0.5f animations:^{ 134 | self.rightButton.superview.transform = Transform; 135 | }]; 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiButton.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | @class YTEmoji; 11 | 12 | @interface YTEmojiButton : UIButton 13 | @property (nonatomic, strong) YTEmoji *emoji; 14 | @end 15 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiButton.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiButton.h" 10 | #import "YTEmoji.h" 11 | 12 | @implementation YTEmojiButton 13 | 14 | - (void)setEmoji:(YTEmoji *)emoji{ 15 | _emoji = emoji; 16 | [self setImage:[UIImage imageNamed:emoji.emojiImage] forState:UIControlStateNormal]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiPage.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | @class YTEmojiM; 11 | 12 | #define EMOJI_IMG_WH 20.0f 13 | 14 | @interface YTEmojiPage : UIButton 15 | @property (nonatomic, strong) YTEmojiM *emojiM; //包含此类表情全部信息 16 | @property (nonatomic, assign) CGPoint offsetStart; //表情开始位置 17 | @property (nonatomic, assign) CGPoint offsetEnd; //表情结束位置 18 | @end 19 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiPage.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiPage.h" 10 | 11 | @implementation YTEmojiPage 12 | 13 | - (CGRect)imageRectForContentRect:(CGRect)contentRect{ 14 | CGRect rect = [super imageRectForContentRect:contentRect]; 15 | rect.size= CGSizeMake(EMOJI_IMG_WH, EMOJI_IMG_WH); 16 | CGFloat W = self.bounds.size.width; 17 | CGFloat H = self.bounds.size.height; 18 | rect.origin = CGPointMake((W-EMOJI_IMG_WH)*0.5f, (H-EMOJI_IMG_WH)*0.5f); 19 | return rect; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiView.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | @class YTEmoji; 11 | 12 | #define EMOJI_VIEW_HEIGHT 205.0f 13 | #define EMOJI_SCROLL_HEIGHT 170.0f 14 | 15 | @protocol YTEmojiViewDelegate 16 | @optional 17 | /* 点击某个表情,将会显示在输入框内 */ 18 | - (void)emojiViewEmoji:(YTEmoji *)emoji; 19 | 20 | /* 点击“删除”按钮 */ 21 | - (void)emojiViewDelete; 22 | 23 | /* 点击“发送”按钮 */ 24 | - (void)emojiViewSend; 25 | @end 26 | 27 | @interface YTEmojiView : UIView 28 | 29 | - (instancetype)initWithDelegate:(id)delegate; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Emoji/EmojiView/YTEmojiView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTEmojiView.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTEmojiBottom.h" 10 | #import "YTEmojiButton.h" 11 | #import "YTEmojiView.h" 12 | #import "YTEmojiFile.h" 13 | #import "YTEmojiPage.h" 14 | 15 | #define EMOJI_PAGECONTROL_HEIGHT 24.0f 16 | 17 | @interface YTEmojiView(){ 18 | YTEmojiPage *currentPageBT; //当前表情页 19 | CGFloat priopPointX; // scrollView 划过的前一个点x坐标 20 | BOOL fromeScroll; // 标记切换表情方法来源 21 | } 22 | @property (nonatomic, assign) id delegate; //代理 23 | @property (nonatomic, strong) UIScrollView *scrollView; //显示表情图片 24 | @property (nonatomic, strong) UIPageControl *pageControl; //图片翻页标示 25 | @property (nonatomic, strong) NSMutableArray *pageBts; //底部切换表情按钮 26 | @property (nonatomic, strong) YTEmojiBottom *bottomView; //底部空间 27 | @end 28 | 29 | @implementation YTEmojiView 30 | 31 | #pragma mark - 初始化 32 | 33 | - (NSMutableArray *)pageBts{ 34 | if (!_pageBts) { 35 | _pageBts = [NSMutableArray array]; 36 | } 37 | return _pageBts; 38 | } 39 | 40 | - (instancetype)initWithDelegate:(id)delegate{ 41 | CGRect frame = CGRectMake(0, 0, WIDTH, EMOJI_VIEW_HEIGHT); 42 | if (self = [super initWithFrame:frame]) { 43 | self.delegate = delegate; 44 | [self initUI]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)setFrame:(CGRect)frame{ 50 | frame.size = CGSizeMake(WIDTH, EMOJI_VIEW_HEIGHT); 51 | [super setFrame:frame]; 52 | } 53 | 54 | - (void)initUI{ 55 | CGRect frame = self.bounds; 56 | frame.size.height = EMOJI_SCROLL_HEIGHT; 57 | [self addScroll:frame]; /*scroll*/ 58 | 59 | frame.origin.y = CGRectGetHeight(frame)- EMOJI_PAGECONTROL_HEIGHT; 60 | frame.size.height = EMOJI_PAGECONTROL_HEIGHT; 61 | [self addPageControl:frame]; /*pagecontrol*/ 62 | 63 | frame.origin.y = CGRectGetHeight(self.bounds) - EMOJI_BOTTOM_HEIGHT; 64 | frame.size.height = EMOJI_BOTTOM_HEIGHT; 65 | [self addBottom:frame];/*bottom*/ 66 | 67 | [self showIcons];//展示表情图标 68 | } 69 | 70 | #pragma mark 添加控件 71 | -(void)addScroll:(CGRect)frame{ 72 | UIScrollView *scroll = [[UIScrollView alloc]initWithFrame:frame]; 73 | scroll.delegate = self; 74 | scroll.pagingEnabled = YES; 75 | scroll.showsHorizontalScrollIndicator = NO; 76 | [self addSubview:scroll]; 77 | self.scrollView = scroll; 78 | } 79 | 80 | -(void)addPageControl:(CGRect)frame{ 81 | UIPageControl *page = [[UIPageControl alloc]initWithFrame:frame]; 82 | page.currentPageIndicatorTintColor = [UIColor lightGrayColor]; 83 | page.pageIndicatorTintColor = [UIColor blackColor]; 84 | page.userInteractionEnabled = NO; 85 | [self addSubview:page]; 86 | self.pageControl = page; 87 | } 88 | 89 | -(void)addBottom:(CGRect)frame{ 90 | YTEmojiBottom *bottom = [[YTEmojiBottom alloc]initWithFrame:frame]; 91 | //page 92 | NSArray *emojiMS = [YTEmojiFile emojiModelS]; 93 | for (int i = 0; i < emojiMS.count; i++) { 94 | CGRect rect = CGRectMake(EMOJI_BOTTOM_BUTTON_WIDTH*i, 0, EMOJI_BOTTOM_BUTTON_WIDTH, EMOJI_BOTTOM_HEIGHT); 95 | YTEmojiPage *pageBt = [[YTEmojiPage alloc]initWithFrame:rect]; 96 | YTEmojiM *em = emojiMS[i]; 97 | pageBt.emojiM = em; 98 | pageBt.tag = i; 99 | [pageBt setImage:[UIImage imageNamed:em.image] forState:UIControlStateNormal]; 100 | [pageBt addTarget:self action:@selector(pageChange:) forControlEvents:UIControlEventTouchUpInside]; 101 | [bottom.scrollView addSubview:pageBt]; 102 | [self.pageBts addObject:pageBt]; 103 | } 104 | //scroll 105 | bottom.scrollView.contentSize = CGSizeMake(emojiMS.count*EMOJI_BOTTOM_BUTTON_WIDTH, 0); 106 | //send 107 | [bottom.rightButton addTarget:self action:@selector(sendButtonAction) forControlEvents:UIControlEventTouchUpInside]; 108 | //+"添加" 109 | //........// 110 | [self addSubview:bottom]; 111 | self.bottomView = bottom; 112 | } 113 | 114 | #pragma mark 表情展示 115 | - (void)showIcons{ 116 | if (self.pageBts.count == 0) return; 117 | for (int i = 0; i < self.pageBts.count; i++) { 118 | YTEmojiPage *pageBt = self.pageBts[i]; 119 | if (i == 0) { 120 | currentPageBT = pageBt; 121 | pageBt.offsetStart = CGPointZero; 122 | pageBt.backgroundColor = [UIColor grayColor]; 123 | self.pageControl.numberOfPages = [pageBt.emojiM countPageAll]; 124 | }else{ 125 | YTEmojiPage *p = self.pageBts[(int)MAX(i-1, 0)]; 126 | pageBt.offsetStart = p.offsetEnd; 127 | } 128 | [self addIconsWithPage:pageBt]; 129 | } 130 | CGFloat widthX = ((YTEmojiPage *)[self.pageBts lastObject]).offsetEnd.x; 131 | self.scrollView.contentSize = CGSizeMake( widthX, 0); 132 | } 133 | 134 | - (void)addIconsWithPage:(YTEmojiPage *)pageBt{ 135 | YTEmojiM *em = pageBt.emojiM; 136 | YTEmojiNorms norms = em.norms; 137 | CGFloat WH = norms.boardWH; 138 | CGFloat spaceB = norms.spaceBoard; 139 | CGFloat spaceV = norms.spaceVerticalityMIN; 140 | CGFloat initX = pageBt.offsetStart.x; 141 | NSInteger countInLine = [em countOneLine]; 142 | NSInteger countInPage = [em countOnePage]; 143 | NSInteger countAllPage = [em countPageAll]; 144 | CGFloat spaceH = (WIDTH-spaceB*2-WH*countInLine)/(float)(countInLine-1); 145 | pageBt.offsetEnd = CGPointMake(pageBt.offsetStart.x+countAllPage*WIDTH, 0); 146 | for (int page = 0; page < countAllPage; page++) {//多少页 147 | NSInteger countIndex = [em countPageIndex:page]; 148 | for (int count = 0; count < countIndex; count++) {//一页多少个 149 | // X:行中第几个,Y:第几行 150 | NSInteger X = count%countInLine; 151 | NSInteger Y = count/countInLine; 152 | CGRect rect = CGRectZero; 153 | rect.size = CGSizeMake( WH, WH); 154 | rect.origin.x = spaceB+(spaceH+WH)*X+page*WIDTH+initX; 155 | rect.origin.y = spaceB+(spaceV+WH)*Y; 156 | 157 | YTEmojiButton *eb = [[YTEmojiButton alloc]initWithFrame:rect]; 158 | eb.emoji = em.icons[(page*countInPage+count)]; 159 | [eb addTarget:self action:@selector(selectorThisIcon:) forControlEvents:UIControlEventTouchUpInside]; 160 | [self.scrollView addSubview:eb]; 161 | UILongPressGestureRecognizer *longTap = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longTapAction:)]; 162 | [eb addGestureRecognizer:longTap]; 163 | } 164 | if (em.type == YTEmojiTypeIcon) { 165 | CGRect frame = CGRectZero; 166 | frame.size = CGSizeMake( WH, WH); 167 | frame.origin.x = spaceB+(spaceH+WH)*(countInLine-1)+page*WIDTH+initX; 168 | frame.origin.y = spaceB+(spaceV+WH)*(em.norms.lines-1); 169 | YTEmojiButton *del = [[YTEmojiButton alloc]initWithFrame:frame]; 170 | [del addTarget:self action:@selector(deleteIcons) forControlEvents:UIControlEventTouchUpInside]; 171 | [del setImage:[UIImage imageNamed:@"btn_del"] forState:UIControlStateNormal]; 172 | [self.scrollView addSubview:del]; 173 | } 174 | } 175 | } 176 | 177 | #pragma mark - 表情栏切换 178 | - (void)pageChange:(YTEmojiPage *)pageBt{ 179 | if (pageBt.tag == currentPageBT.tag) { 180 | fromeScroll = NO; 181 | return; 182 | } 183 | YTEmojiM *emojiM = pageBt.emojiM; 184 | switch (emojiM.type) { 185 | case YTEmojiTypeChartlet: 186 | [self.bottomView rightButtonAnimationHiden:YES]; 187 | break; 188 | case YTEmojiTypeIcon: 189 | [self.bottomView rightButtonAnimationHiden:NO]; 190 | break; 191 | default: 192 | break; 193 | } 194 | pageBt.backgroundColor = [UIColor grayColor]; 195 | _pageControl.numberOfPages = [emojiM countPageAll]; 196 | if (!fromeScroll) { 197 | [self.scrollView setContentOffset:pageBt.offsetStart]; 198 | priopPointX = pageBt.offsetStart.x; 199 | _pageControl.currentPage = 0; 200 | }else{ 201 | fromeScroll = NO; 202 | } 203 | currentPageBT.backgroundColor = [UIColor clearColor]; 204 | currentPageBT = pageBt; 205 | } 206 | 207 | #pragma mark - Scroll View Delegate 208 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 209 | if ((scrollView != self.scrollView)||(!scrollView.isDragging)) return; 210 | [self scrollViewDidDragging:scrollView]; 211 | } 212 | 213 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 214 | [self scrollViewDidDragging:scrollView]; 215 | } 216 | 217 | - (void)scrollViewDidDragging:(UIScrollView*)scrollView{ 218 | CGFloat offsetX = scrollView.contentOffset.x; 219 | NSInteger tag = currentPageBT.tag; 220 | YTEmojiPage *pageBt = nil; 221 | if (offsetX > priopPointX) { //右划 222 | tag = MIN(tag+1, self.pageBts.count-1); 223 | pageBt = self.pageBts[tag]; 224 | if (fabs(pageBt.offsetStart.x-offsetX)<10) { 225 | fromeScroll = YES; 226 | [self pageChange:pageBt]; 227 | } 228 | }else{ //左划 229 | tag = MAX(0, tag-1); 230 | pageBt = self.pageBts[tag]; 231 | if (fabs(pageBt.offsetEnd.x-offsetX-WIDTH)<10) { 232 | fromeScroll = YES; 233 | [self pageChange:pageBt]; 234 | } 235 | } 236 | self.pageControl.currentPage =((scrollView.contentOffset.x - currentPageBT.offsetStart.x)/scrollView.bounds.size.width)+0.5; 237 | priopPointX = offsetX; 238 | } 239 | 240 | #pragma mark - 长按表情动画 241 | - (void)longTapAction:(UILongPressGestureRecognizer*)longTap{ 242 | YTEmojiButton *eb = (YTEmojiButton*)longTap.view; 243 | switch (longTap.state) { 244 | case UIGestureRecognizerStateBegan: 245 | [self showEmotionName:eb.emoji.emojiName]; 246 | break; 247 | case UIGestureRecognizerStateEnded: 248 | [self selectorThisIcon:eb]; 249 | break; 250 | default: 251 | break; 252 | } 253 | 254 | } 255 | 256 | - (void)showEmotionName:(NSString *)name { 257 | #define W 80.0f 258 | #define H 40.0f 259 | CGFloat x = (self.superview.frame.size.width-80.0f)/2.0f; 260 | UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(x, self.superview.frame.origin.y, W, H)]; 261 | 262 | lab.text = name; 263 | lab.alpha = 0.1f; 264 | lab.layer.masksToBounds = YES; 265 | lab.layer.cornerRadius = 10.0f; 266 | lab.textColor = [UIColor whiteColor]; 267 | lab.backgroundColor = [UIColor grayColor]; 268 | lab.textAlignment = NSTextAlignmentCenter; 269 | lab.font = [UIFont systemFontOfSize:15.0f]; 270 | 271 | [self.superview.superview addSubview:lab]; 272 | [UIView animateWithDuration:0.4f animations:^{ 273 | lab.frame = CGRectMake(x, -H, W, H); 274 | lab.alpha = 1.0f; 275 | } completion:^(BOOL finished) { 276 | if (finished) { 277 | [UIView animateWithDuration:0.3f delay:0.5f options:UIViewAnimationOptionCurveEaseOut animations:^{ 278 | lab.alpha = 0.0f; 279 | } completion:^(BOOL finished) { 280 | if (finished) [lab removeFromSuperview]; 281 | }]; 282 | } 283 | }]; 284 | } 285 | 286 | #pragma mark - self delegate action 287 | -(void)selectorThisIcon:(YTEmojiButton*)sender{ 288 | if ([self.delegate respondsToSelector:@selector(emojiViewEmoji:)]) { 289 | [self.delegate emojiViewEmoji:sender.emoji]; 290 | } 291 | } 292 | 293 | -(void)deleteIcons{ 294 | if ([self.delegate respondsToSelector:@selector(emojiViewDelete)]) { 295 | [self.delegate emojiViewDelete]; 296 | } 297 | } 298 | 299 | -(void)sendButtonAction{ 300 | if ([self.delegate respondsToSelector:@selector(emojiViewSend)]) { 301 | [self.delegate emojiViewSend]; 302 | } 303 | } 304 | 305 | @end 306 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/More/YTMoreView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTMoreView.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/1. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | typedef NS_ENUM(NSInteger, YTMoreViewTypeAction){ 11 | YTMoreViewTypeActionNon = 0, // 12 | YTMoreViewTypeActionPhoto, 13 | YTMoreViewTypeActionCamera 14 | }; 15 | @protocol YTMoreViewDelegate 16 | 17 | /**moewView包含控件事件,有可能是后期扩展*/ 18 | - (void)moreViewType:(YTMoreViewTypeAction)type; 19 | @end 20 | 21 | @interface YTMoreView : UIView 22 | 23 | @property (nonatomic, strong) id delegate; //代理 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/More/YTMoreView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTMoreView.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/1. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTDeviceTest.h" 10 | #import "YTMoreView.h" 11 | 12 | @interface YTIcons : UIButton 13 | 14 | @end 15 | 16 | @implementation YTIcons 17 | #define BT_IMG_WH 50.0f //自身宽和内部image宽高 18 | #define BT_H 70.0f //自身高 19 | 20 | - (instancetype)initWithFrame:(CGRect)frame{ 21 | if (self = [super initWithFrame:frame]) { 22 | self.titleLabel.font = [UIFont systemFontOfSize:14]; 23 | self.titleLabel.textAlignment = NSTextAlignmentCenter; 24 | [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 25 | } 26 | return self; 27 | } 28 | 29 | - (CGRect)imageRectForContentRect:(CGRect)contentRect{ 30 | contentRect = CGRectMake(0, 0, BT_IMG_WH, BT_IMG_WH); 31 | return contentRect; 32 | } 33 | 34 | - (CGRect)titleRectForContentRect:(CGRect)contentRect{ 35 | contentRect = CGRectMake(0, BT_IMG_WH, BT_IMG_WH, BT_H-BT_IMG_WH); 36 | return contentRect; 37 | } 38 | 39 | @end 40 | 41 | @interface YTMoreView(){ 42 | NSArray *images; 43 | NSArray *hightImages; 44 | NSArray *titles; 45 | NSArray *types; 46 | } 47 | @property (nonatomic, strong) UIScrollView * scrollView; 48 | @end 49 | 50 | @implementation YTMoreView 51 | 52 | - (instancetype)initWithFrame:(CGRect)frame{ 53 | if (self = [super initWithFrame:frame]) { 54 | self.backgroundColor = [UIColor whiteColor]; 55 | [self initUI]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)initUI{ 61 | [self addResource]; 62 | [self addScrollView]; 63 | [self addIcons]; 64 | } 65 | 66 | - (void)addResource{ 67 | images = @[@"photo_nomal",@"camera_nomal"]; 68 | hightImages = @[@"photo_down",@"camera_down"]; 69 | titles = @[@"图片",@"照相"]; 70 | types = @[@(YTMoreViewTypeActionPhoto),@(YTMoreViewTypeActionCamera)]; 71 | } 72 | 73 | - (void)addScrollView{ 74 | UIScrollView * scroll = [[UIScrollView alloc]initWithFrame:self.bounds]; 75 | scroll.bounces = NO; 76 | scroll.pagingEnabled = YES; 77 | scroll.showsVerticalScrollIndicator = NO; 78 | scroll.showsHorizontalScrollIndicator = NO; 79 | [self addSubview:scroll]; 80 | self.scrollView = scroll; 81 | } 82 | 83 | - (void)addIcons{ 84 | CGSize size = self.bounds.size; 85 | CGFloat spaceV = (size.width-BT_IMG_WH*4.0f)/5.0f; 86 | CGFloat spaceH = (size.height-BT_H*2.0f)/3.0f; 87 | 88 | CGRect rect = CGRectMake(0, 0, BT_IMG_WH, BT_H); 89 | for (int i =0; i < images.count; i++) {//暂时不考虑第二页 90 | NSInteger V = i%4; 91 | NSInteger H = i/4; 92 | rect.origin.x = spaceV+V*(spaceV+BT_IMG_WH); 93 | rect.origin.y = spaceH+H*(spaceH+BT_H); 94 | YTIcons * icon = [[YTIcons alloc]initWithFrame:rect]; 95 | icon.tag = ((NSNumber *)types[i]).integerValue; 96 | [icon setImage:[UIImage imageNamed:images[i]] forState:UIControlStateNormal]; 97 | [icon setImage:[UIImage imageNamed:hightImages[i]] forState:UIControlStateHighlighted]; 98 | [icon setTitle:titles[i] forState:UIControlStateNormal]; 99 | [icon addTarget:self action:@selector(iconsAction:) forControlEvents:UIControlEventTouchUpInside]; 100 | [self.scrollView addSubview:icon]; 101 | 102 | } 103 | } 104 | 105 | - (void)iconsAction:(UIButton *)icon{ 106 | switch ((YTMoreViewTypeAction)icon.tag) { 107 | case YTMoreViewTypeActionPhoto: 108 | if ([YTDeviceTest userAuthorizationPhotoStatus]) { 109 | [self sourceType:YTMoreViewTypeActionPhoto]; 110 | } 111 | break; 112 | case YTMoreViewTypeActionCamera: 113 | if ([YTDeviceTest userAuthorizationCameraStatus]) { 114 | [self sourceType:YTMoreViewTypeActionCamera]; 115 | } 116 | break; 117 | default: 118 | break; 119 | } 120 | } 121 | 122 | #pragma mark - delegate 123 | - (void)sourceType:(YTMoreViewTypeAction)type{ 124 | if ([self.delegate respondsToSelector:@selector(moreViewType:)]) { 125 | [self.delegate moreViewType:type]; 126 | } 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Other/SLGrowingTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SLGrowingTextView.h 3 | // 4 | // Created by Kent on 29-06-2014 5 | // 6 | 7 | @import UIKit; 8 | 9 | 10 | extern const NSInteger SLMinNumOfLines; 11 | extern const NSInteger SLMaxNumOfLines; 12 | extern const CGFloat SLFrameChangeDuration; 13 | 14 | 15 | @class SLGrowingTextView; 16 | 17 | 18 | @protocol SLGrowingTextViewDelegate 19 | 20 | @optional 21 | - (BOOL)growingTextViewShouldBeginEditing:(SLGrowingTextView *)growingTextView; 22 | - (BOOL)growingTextViewShouldEndEditing:(SLGrowingTextView *)growingTextView; 23 | 24 | - (void)growingTextViewDidBeginEditing:(SLGrowingTextView *)growingTextView; 25 | - (void)growingTextViewDidEndEditing:(SLGrowingTextView *)growingTextView; 26 | 27 | - (BOOL)growingTextView:(SLGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; 28 | - (void)growingTextViewDidChange:(SLGrowingTextView *)growingTextView; 29 | 30 | - (void)growingTextView:(SLGrowingTextView *)growingTextView shouldChangeHeight:(CGFloat)height; 31 | 32 | - (void)growingTextViewDidChangeSelection:(SLGrowingTextView *)growingTextView; 33 | - (BOOL)growingTextViewShouldReturn:(SLGrowingTextView *)growingTextView; 34 | @end 35 | 36 | 37 | /** 38 | * \class 39 | * \brief Frame自适应的textView。当用户输入时,textView会根据font和其他一些因素计算最佳的高度, 40 | * 通过shouldChangeHeight通知代理对象,代理对象可以修改textView的frame,由于不知道 41 | * 上下文,textView并不会自己修改自己的frame。代理对象可以用SLFrameChangeDuration用动画 42 | * 的形式修改frame。 43 | * 第一次创建textView的时候,用sizeThatFits计算最佳高度,并设置textView的frame。 44 | * 此外,textView用户可以设置最小和最大行数,行数会影响textView的最大和最小高度。 45 | */ 46 | #import "YTTextView.h" 47 | @interface SLGrowingTextView : UIView 48 | 49 | @property (nonatomic, weak) id delegate; 50 | 51 | /** 52 | * default nil, it will be created when accessed 53 | */ 54 | @property (nonatomic, strong) UIView *backgroundView; 55 | @property (nonatomic, strong, readonly) YTTextView *internalTextView; 56 | 57 | // 设置最大和最小行数 58 | @property (nonatomic, assign) int maxNumberOfLines; // default 1 59 | @property (nonatomic, assign) int minNumberOfLines; // default 5 60 | 61 | @property (nonatomic, copy) NSString *placeholder; // default nil 62 | 63 | /** 64 | * 内部textView的insets 65 | */ 66 | @property (nonatomic, assign) UIEdgeInsets contentInset; 67 | 68 | // UITextView属性 69 | @property (nonatomic, strong) NSString *text; 70 | @property (nonatomic, strong) UIFont *font; 71 | @property (nonatomic, strong) UIColor *textColor; 72 | @property (nonatomic, assign) NSTextAlignment textAlignment; // default is UITextAlignmentLeft 73 | @property (nonatomic, assign) NSRange selectedRange; // only ranges of length 0 are supported 74 | @property (nonatomic, getter=isEditable) BOOL editable; 75 | @property (nonatomic, assign) UIDataDetectorTypes dataDetectorTypes; 76 | @property (nonatomic, assign) UIReturnKeyType returnKeyType; 77 | @property (nonatomic, assign) UIKeyboardAppearance keyboardAppearance; 78 | @property (nonatomic, assign) BOOL enablesReturnKeyAutomatically; 79 | 80 | - (BOOL)canBecomeFirstResponder; 81 | - (BOOL)becomeFirstResponder; 82 | - (BOOL)canResignFirstResponder; 83 | - (BOOL)resignFirstResponder; 84 | - (BOOL)isFirstResponder; 85 | 86 | - (BOOL)hasText; 87 | - (void)scrollRangeToVisible:(NSRange)range; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Other/SLGrowingTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SLGrowingTextView.m 3 | // 4 | // Created by Kent on 29-06-2014 5 | // 6 | 7 | #import "SLGrowingTextView.h" 8 | 9 | 10 | const NSInteger SLMinNumOfLines = 1; 11 | const NSInteger SLMaxNumOfLines = 5; 12 | const CGFloat SLFrameChangeDuration = 0.1; 13 | 14 | 15 | @interface SLGrowingTextView () 16 | 17 | @property (nonatomic, assign) CGFloat internalTextViewSizeDelta; 18 | 19 | // 最大和最小高度,根据行数、contentInset计算而来 20 | @property (nonatomic, assign) int minHeight; 21 | @property (nonatomic, assign) int maxHeight; 22 | 23 | @property (nonatomic, strong) UILabel *placeholderLabel; 24 | 25 | @end 26 | 27 | @implementation SLGrowingTextView 28 | 29 | @synthesize backgroundView = _backgroundView; 30 | 31 | @synthesize minNumberOfLines = _minNumberOfLines; 32 | @synthesize maxNumberOfLines = _maxNumberOfLines; 33 | 34 | @synthesize font = _font; 35 | @synthesize textColor = _textColor; 36 | @synthesize textAlignment = _textAlignment; 37 | @synthesize selectedRange = _selectedRange; 38 | @synthesize editable = _editable; 39 | @synthesize dataDetectorTypes = _dataDetectorTypes; 40 | @synthesize returnKeyType = _returnKeyType; 41 | @synthesize contentInset = _contentInset; 42 | 43 | 44 | - (id)initWithCoder:(NSCoder *)aDecoder { 45 | if ((self = [super initWithCoder:aDecoder])) { 46 | [self commonInitialiser]; 47 | } 48 | return self; 49 | } 50 | 51 | - (id)initWithFrame:(CGRect)frame { 52 | if ((self = [super initWithFrame:frame])) { 53 | [self commonInitialiser]; 54 | } 55 | return self; 56 | } 57 | 58 | - (void)commonInitialiser { 59 | self.backgroundColor = [UIColor whiteColor]; 60 | 61 | CGRect frame = UIEdgeInsetsInsetRect(self.bounds, _contentInset); 62 | _internalTextView = [[YTTextView alloc] initWithFrame:frame]; 63 | _internalTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 64 | _internalTextView.delegate = self; 65 | 66 | _internalTextView.font = [UIFont systemFontOfSize:15.0]; 67 | _internalTextView.backgroundColor = [UIColor clearColor]; 68 | 69 | _internalTextView.showsHorizontalScrollIndicator = NO; 70 | _internalTextView.showsVerticalScrollIndicator = YES; 71 | 72 | _internalTextView.scrollEnabled = YES; 73 | 74 | if ([_internalTextView respondsToSelector:@selector(textContainerInset)]) { 75 | _internalTextView.textContainerInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0); 76 | _internalTextViewSizeDelta = 0.0; 77 | } else { 78 | // iOS7之前,textView.contentInset == UIEdgeZero,但绘制文本时, 79 | // 却缩进了,缩进值大概为{7.0, 0.0, 7.0, 0.0},这样文本的上下有空白,不会紧贴边框。 80 | // 这里取消掉,但用sizeThatFits计算size时,系统还是考虑了这个缩进,我们用sizeThatFits计算时, 81 | // 要去除缩进,即_internalTextViewSizeDelta的值 82 | // 在不同fontSize下,这个缩进值不变 83 | _internalTextView.contentInset = UIEdgeInsetsMake(-7.0, 0.0, -7.0, 0.0); 84 | _internalTextViewSizeDelta = -7.0 * 2.0; 85 | } 86 | 87 | [self addSubview:_internalTextView]; 88 | 89 | [self setMinNumberOfLines:SLMinNumOfLines]; 90 | [self setMaxNumberOfLines:SLMaxNumOfLines]; 91 | } 92 | 93 | - (CGSize)sizeThatFits:(CGSize)size { 94 | CGFloat estimateWidth = self.bounds.size.width - _contentInset.left - _contentInset.right; 95 | CGFloat internalTextViewHeight = [self internalTextViewSizeThatFits:CGSizeMake(estimateWidth, CGFLOAT_MAX)].height; 96 | internalTextViewHeight = MAX(MIN(internalTextViewHeight, _maxHeight), _minHeight); 97 | 98 | return CGSizeMake(self.bounds.size.width, 99 | _contentInset.top + _contentInset.bottom + internalTextViewHeight); 100 | } 101 | 102 | #pragma mark - Setters & getters 103 | 104 | - (void)setContentInset:(UIEdgeInsets)inset { 105 | _contentInset = inset; 106 | 107 | _internalTextView.frame = UIEdgeInsetsInsetRect(self.bounds, self.contentInset); 108 | 109 | [self performSelector:@selector(textViewDidChange:) withObject:_internalTextView]; 110 | } 111 | 112 | - (UIEdgeInsets)contentInset { 113 | return _contentInset; 114 | } 115 | 116 | - (UIView *)backgroundView { 117 | if (!_backgroundView) { 118 | _backgroundView = [[UIImageView alloc] initWithFrame:self.bounds]; 119 | _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 120 | [self insertSubview:_backgroundView atIndex:0]; 121 | } 122 | return _backgroundView; 123 | } 124 | 125 | - (void)setBackgroundView:(UIView *)backgroundView { 126 | if (backgroundView != _backgroundView && 127 | backgroundView != nil) { 128 | 129 | if ([backgroundView superview]) { 130 | [backgroundView removeFromSuperview]; 131 | } 132 | 133 | if ([_backgroundView superview]) { 134 | [_backgroundView removeFromSuperview]; 135 | } 136 | _backgroundView = nil; 137 | 138 | _backgroundView = backgroundView; 139 | _backgroundView.frame = self.bounds; 140 | _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 141 | [self insertSubview:backgroundView atIndex:0]; 142 | } 143 | } 144 | 145 | - (void)setPlaceholder:(NSString *)placeholder { 146 | _placeholder = [placeholder copy]; 147 | if (placeholder.length) { 148 | self.placeholderLabel.text = placeholder; 149 | } else { 150 | self.placeholderLabel = nil; 151 | } 152 | 153 | [self showOrHidePlaceholder]; 154 | } 155 | 156 | - (UILabel *)placeholderLabel { 157 | if (!_placeholderLabel) { 158 | _placeholderLabel = [[UILabel alloc] initWithFrame:CGRectInset(self.bounds, 5.0, 0.0)]; 159 | _placeholderLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 160 | _placeholderLabel.font = _internalTextView.font; 161 | _placeholderLabel.textColor = [UIColor lightGrayColor]; 162 | _placeholderLabel.backgroundColor = [UIColor clearColor]; 163 | [self insertSubview:_placeholderLabel belowSubview:_internalTextView]; 164 | } 165 | return _placeholderLabel; 166 | } 167 | 168 | - (void)setMaxNumberOfLines:(int)n { 169 | if (_maxNumberOfLines != n) { 170 | _maxHeight = [self estimateIntervalTextViewHeightWithLines:n]; 171 | 172 | _maxNumberOfLines = n; 173 | } 174 | } 175 | 176 | - (void)setMinNumberOfLines:(int)m { 177 | if (_minNumberOfLines != m) { 178 | _minHeight = [self estimateIntervalTextViewHeightWithLines:m]; 179 | 180 | _minNumberOfLines = m; 181 | } 182 | } 183 | 184 | #pragma mark - Instance methods 185 | 186 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 187 | [_internalTextView becomeFirstResponder]; 188 | } 189 | 190 | - (BOOL)canBecomeFirstResponder { 191 | return [_internalTextView canBecomeFirstResponder]; 192 | } 193 | 194 | - (BOOL)becomeFirstResponder { 195 | return [_internalTextView becomeFirstResponder]; 196 | } 197 | 198 | - (BOOL)canResignFirstResponder { 199 | return [_internalTextView canResignFirstResponder]; 200 | } 201 | 202 | - (BOOL)resignFirstResponder { 203 | return [_internalTextView resignFirstResponder]; 204 | } 205 | 206 | - (BOOL)isFirstResponder { 207 | return [_internalTextView isFirstResponder]; 208 | } 209 | 210 | - (BOOL)hasText{ 211 | return [_internalTextView hasText]; 212 | } 213 | 214 | - (void)scrollRangeToVisible:(NSRange)range { 215 | [_internalTextView scrollRangeToVisible:range]; 216 | } 217 | 218 | /////////////////////////////////////////////////////////////////////////////////////////////////// 219 | #pragma mark - UITextView properties 220 | /////////////////////////////////////////////////////////////////////////////////////////////////// 221 | 222 | - (void)setText:(NSString *)newText { 223 | _internalTextView.text = newText; 224 | 225 | [self performSelector:@selector(textViewDidChange:) withObject:_internalTextView]; 226 | } 227 | 228 | - (NSString *)text { 229 | return _internalTextView.text; 230 | } 231 | 232 | /////////////////////////////////////////////////////////////////////////////////////////////////// 233 | 234 | - (void)setFont:(UIFont *)afont { 235 | _internalTextView.font = afont; 236 | _placeholderLabel.font = afont; 237 | 238 | _maxHeight = [self estimateIntervalTextViewHeightWithLines:_maxNumberOfLines]; 239 | _minHeight = [self estimateIntervalTextViewHeightWithLines:_minNumberOfLines]; 240 | 241 | [self performSelector:@selector(textViewDidChange:) withObject:_internalTextView]; 242 | } 243 | 244 | - (UIFont *)font { 245 | return _internalTextView.font; 246 | } 247 | 248 | /////////////////////////////////////////////////////////////////////////////////////////////////// 249 | 250 | -(void)setTextColor:(UIColor *)color { 251 | _internalTextView.textColor = color; 252 | } 253 | 254 | -(UIColor*)textColor{ 255 | return _internalTextView.textColor; 256 | } 257 | 258 | /////////////////////////////////////////////////////////////////////////////////////////////////// 259 | 260 | - (void)setBackgroundColor:(UIColor *)backgroundColor { 261 | [super setBackgroundColor:backgroundColor]; 262 | _internalTextView.backgroundColor = backgroundColor; 263 | } 264 | 265 | - (UIColor*)backgroundColor { 266 | return _internalTextView.backgroundColor; 267 | } 268 | 269 | /////////////////////////////////////////////////////////////////////////////////////////////////// 270 | 271 | -(void)setTextAlignment:(NSTextAlignment)aligment { 272 | _internalTextView.textAlignment = aligment; 273 | } 274 | 275 | -(NSTextAlignment)textAlignment { 276 | return _internalTextView.textAlignment; 277 | } 278 | 279 | /////////////////////////////////////////////////////////////////////////////////////////////////// 280 | 281 | - (void)setSelectedRange:(NSRange)range { 282 | _internalTextView.selectedRange = range; 283 | } 284 | 285 | - (NSRange)selectedRange { 286 | return _internalTextView.selectedRange; 287 | } 288 | 289 | /////////////////////////////////////////////////////////////////////////////////////////////////// 290 | 291 | - (void)setEditable:(BOOL)beditable { 292 | _internalTextView.editable = beditable; 293 | } 294 | 295 | - (BOOL)isEditable { 296 | return _internalTextView.editable; 297 | } 298 | 299 | /////////////////////////////////////////////////////////////////////////////////////////////////// 300 | 301 | - (void)setReturnKeyType:(UIReturnKeyType)keyType { 302 | _internalTextView.returnKeyType = keyType; 303 | } 304 | 305 | - (UIReturnKeyType)returnKeyType { 306 | return _internalTextView.returnKeyType; 307 | } 308 | 309 | - (void)setKeyboardAppearance:(UIKeyboardAppearance)keyboardAppearance { 310 | _internalTextView.keyboardAppearance = keyboardAppearance; 311 | } 312 | 313 | - (UIKeyboardAppearance)keyboardAppearance { 314 | return _internalTextView.keyboardAppearance; 315 | } 316 | 317 | /////////////////////////////////////////////////////////////////////////////////////////////////// 318 | 319 | - (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically { 320 | _internalTextView.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically; 321 | } 322 | 323 | - (BOOL)enablesReturnKeyAutomatically { 324 | return _internalTextView.enablesReturnKeyAutomatically; 325 | } 326 | 327 | /////////////////////////////////////////////////////////////////////////////////////////////////// 328 | 329 | -(void)setDataDetectorTypes:(UIDataDetectorTypes)datadetector { 330 | _internalTextView.dataDetectorTypes = datadetector; 331 | } 332 | 333 | -(UIDataDetectorTypes)dataDetectorTypes { 334 | return _internalTextView.dataDetectorTypes; 335 | } 336 | 337 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 338 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 339 | #pragma mark - UITextViewDelegate 340 | 341 | 342 | /////////////////////////////////////////////////////////////////////////////////////////////////// 343 | - (BOOL)textViewShouldBeginEditing:(UITextView *)textView { 344 | if ([self.delegate respondsToSelector:@selector(growingTextViewShouldBeginEditing:)]) { 345 | return [self.delegate growingTextViewShouldBeginEditing:self]; 346 | 347 | } else { 348 | return YES; 349 | } 350 | } 351 | 352 | 353 | /////////////////////////////////////////////////////////////////////////////////////////////////// 354 | - (BOOL)textViewShouldEndEditing:(UITextView *)textView { 355 | if ([self.delegate respondsToSelector:@selector(growingTextViewShouldEndEditing:)]) { 356 | return [self.delegate growingTextViewShouldEndEditing:self]; 357 | 358 | } else { 359 | return YES; 360 | } 361 | } 362 | 363 | 364 | /////////////////////////////////////////////////////////////////////////////////////////////////// 365 | - (void)textViewDidBeginEditing:(UITextView *)textView { 366 | if ([self.delegate respondsToSelector:@selector(growingTextViewDidBeginEditing:)]) { 367 | [self.delegate growingTextViewDidBeginEditing:self]; 368 | } 369 | } 370 | 371 | 372 | /////////////////////////////////////////////////////////////////////////////////////////////////// 373 | - (void)textViewDidEndEditing:(UITextView *)textView { 374 | [self showOrHidePlaceholder]; 375 | 376 | if ([self.delegate respondsToSelector:@selector(growingTextViewDidEndEditing:)]) { 377 | [self.delegate growingTextViewDidEndEditing:self]; 378 | } 379 | } 380 | 381 | /////////////////////////////////////////////////////////////////////////////////////////////////// 382 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range 383 | replacementText:(NSString *)atext { 384 | 385 | //weird 1 pixel bug when clicking backspace when textView is empty 386 | if (![textView hasText] && [atext isEqualToString:@""]) { 387 | return NO; 388 | } 389 | 390 | //Added by bretdabaker: sometimes we want to handle this ourselves 391 | if ([self.delegate respondsToSelector:@selector(growingTextView:shouldChangeTextInRange:replacementText:)]) { 392 | return [self.delegate growingTextView:self shouldChangeTextInRange:range replacementText:atext]; 393 | } 394 | 395 | if ([atext isEqualToString:@"\n"]) { 396 | if ([self.delegate respondsToSelector:@selector(growingTextViewShouldReturn:)]) { 397 | if (![self.delegate performSelector:@selector(growingTextViewShouldReturn:) withObject:self]) { 398 | return YES; 399 | } else { 400 | [textView resignFirstResponder]; 401 | return NO; 402 | } 403 | } 404 | } 405 | 406 | return YES; 407 | } 408 | 409 | /////////////////////////////////////////////////////////////////////////////////////////////////// 410 | - (void)textViewDidChange:(UITextView *)textView { 411 | // placeholder text label 412 | [self showOrHidePlaceholder]; 413 | 414 | CGFloat needHeight = [self internalTextViewSizeThatFits:CGSizeMake(CGRectGetWidth(_internalTextView.bounds), CGFLOAT_MAX)].height; 415 | CGFloat internalViewNewHeight = MIN(MAX(needHeight, _minHeight), _maxHeight); 416 | 417 | CGFloat selfNewHeight = internalViewNewHeight + _contentInset.top + _contentInset.bottom; 418 | CGFloat currentHeight = CGRectGetHeight(self.bounds); 419 | if (ceilf(currentHeight) != ceilf(selfNewHeight)) { 420 | 421 | // iOS6,iOS7,iOS8下: 422 | // 当前文本行数小于最大行数时,如果scrollEnabled为YES,调整self的frame, 423 | // 文本的显示异常,插入符不在最后一行,插入符下面还有一个空白行。 424 | BOOL shouldUseScrollIndicators = NO; 425 | if (internalViewNewHeight >= _maxHeight) { 426 | if(!_internalTextView.scrollEnabled){ 427 | _internalTextView.scrollEnabled = YES; 428 | shouldUseScrollIndicators = YES; 429 | } 430 | } else { 431 | if (_internalTextView.scrollEnabled) { 432 | _internalTextView.scrollEnabled = NO; 433 | } 434 | } 435 | 436 | if ([self.delegate respondsToSelector:@selector(growingTextView:shouldChangeHeight:)]) { 437 | [self.delegate growingTextView:self shouldChangeHeight:selfNewHeight]; 438 | } 439 | 440 | if (internalViewNewHeight >= _maxHeight) { 441 | // ios7下,文本高度过高时,需要设置textContainer.size,否则不能scroll,且有些文本没有绘制。 442 | if ([_internalTextView respondsToSelector:@selector(textContainer)]) { 443 | // 需要post消息调用让插入符可见。(可能是_internalTextView.scrollEnabled为NO导致) 444 | [self performSelector:@selector(scrollToCaretAnimated:) withObject:@YES afterDelay:SLFrameChangeDuration]; 445 | } else { 446 | // 自动滑动使插入符可见。(可能是_internalTextView.scrollEnabled为NO导致) 447 | // iOS7前,粘贴超过maxLine的文本时,使插入符可见 448 | [self scrollToCaretAnimated:YES]; 449 | } 450 | 451 | [_internalTextView flashScrollIndicators]; 452 | } 453 | } 454 | 455 | if ([self.delegate respondsToSelector:@selector(growingTextViewDidChange:)]) { 456 | [self.delegate growingTextViewDidChange:self]; 457 | } 458 | } 459 | 460 | /////////////////////////////////////////////////////////////////////////////////////////////////// 461 | - (void)textViewDidChangeSelection:(UITextView *)textView { 462 | if ([self.delegate respondsToSelector:@selector(growingTextViewDidChangeSelection:)]) { 463 | [self.delegate growingTextViewDidChangeSelection:self]; 464 | } 465 | } 466 | 467 | #pragma mark - Private 468 | 469 | - (void)showOrHidePlaceholder { 470 | self.placeholderLabel.hidden = (_internalTextView.text.length > 0); 471 | } 472 | 473 | - (void)scrollToCaretAnimated:(BOOL)animated { 474 | CGRect rect = [_internalTextView caretRectForPosition:_internalTextView.selectedTextRange.end]; 475 | [_internalTextView scrollRectToVisible:rect animated:YES]; 476 | } 477 | 478 | - (CGFloat)estimateIntervalTextViewHeightWithLines:(NSInteger)lineCount { 479 | NSString *saveText = _internalTextView.text; 480 | NSString *newText = @"|W|"; 481 | 482 | _internalTextView.delegate = nil; 483 | _internalTextView.hidden = YES; 484 | 485 | for (int i = 1; i < lineCount; ++i) { 486 | newText = [newText stringByAppendingString:@"\n|W|"]; 487 | } 488 | 489 | _internalTextView.text = newText; 490 | 491 | CGFloat height = [_internalTextView sizeThatFits:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)].height; 492 | 493 | _internalTextView.text = saveText; 494 | _internalTextView.hidden = NO; 495 | _internalTextView.delegate = self; 496 | 497 | return height; 498 | } 499 | 500 | - (CGSize)internalTextViewSizeThatFits:(CGSize)constraintSize { 501 | CGSize size = [_internalTextView sizeThatFits:constraintSize]; 502 | size.height += _internalTextViewSizeDelta; 503 | return size; 504 | } 505 | 506 | @end 507 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Other/YTTextAttachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTTextAttachment.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface YTTextAttachment : NSTextAttachment 12 | 13 | @property (nonatomic, strong) NSString *emojiCode; //编码字符 14 | 15 | /** 插入表情到attri中 */ 16 | - (void)insertAttri:(NSMutableAttributedString *)attri font:(UIFont *)font; 17 | @end 18 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Other/YTTextAttachment.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTTextAttachment.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTTextAttachment.h" 10 | 11 | @implementation YTTextAttachment 12 | 13 | - (instancetype)initWithData:(NSData *)contentData ofType:(NSString *)uti{ 14 | if (self = [super initWithData:contentData ofType:uti]) { 15 | if (!self.image) { 16 | self.image = [UIImage imageWithData:contentData]; 17 | } 18 | } 19 | return self; 20 | } 21 | 22 | - (void)insertAttri:(NSMutableAttributedString *)attri font:(UIFont *)font{ 23 | 24 | [attri appendAttributedString:[NSAttributedString attributedStringWithAttachment:self]]; 25 | NSRange range = NSMakeRange(attri.length-1, 1); 26 | NSParagraphStyle * paragraph = [NSParagraphStyle defaultParagraphStyle]; 27 | NSDictionary * attrs = @{NSAttachmentAttributeName:self, 28 | NSFontAttributeName:font, 29 | NSParagraphStyleAttributeName:paragraph}; 30 | [attri setAttributes:attrs range:range]; 31 | } 32 | 33 | // 重写表情加入字符串位置具体信息 可根据具体情况设置不同位置 34 | - (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex{ 35 | 36 | return CGRectMake(0, -2, lineFrag.size.height-2, lineFrag.size.height-2); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Other/YTTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTTextView.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface YTTextView : UITextView 13 | /* 返回一个干净的字符串,主要去掉编码和属性字符 */ 14 | @property (nonatomic, strong, readonly) NSString *clearText; 15 | 16 | /* 17 | ** 通过传入source 返回一个有可能带属性和表情编码的字符串 18 | ** fout color 字体大小和颜色 默认15,黑色 19 | ** offset 设置基线偏移值,取值为 NSNumber(float),正值上偏,负值下偏 20 | */ 21 | + (NSAttributedString *)getAttributedText:(NSString *)source Font:(UIFont *)font Color:(UIColor *)color Offset:(CGFloat)offset; 22 | 23 | // 快速插入一个表情 24 | + (BOOL)insertAttri:(NSMutableAttributedString *)attri imageName:(NSString *)imageName font:(UIFont *)font; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/Other/YTTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTTextView.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTTextAttachment.h" 10 | #import "UIImage+YTGif.h" 11 | #import "YTTextView.h" 12 | 13 | @implementation YTTextView 14 | 15 | - (NSString *)clearText{ 16 | NSMutableAttributedString *result = [self.attributedText mutableCopy]; 17 | NSRange range = NSMakeRange(0, self.attributedText.length); 18 | [result enumerateAttribute:NSAttachmentAttributeName inRange:range options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { 19 | if (value && [value isKindOfClass:[YTTextAttachment class]]) { 20 | YTTextAttachment *attach = (YTTextAttachment *)value; 21 | [result deleteCharactersInRange:range]; 22 | [result insertAttributedString:[[NSAttributedString alloc] initWithString:attach.emojiCode] atIndex:range.location]; 23 | } 24 | }]; 25 | return result.string; 26 | } 27 | 28 | + (NSAttributedString *)getAttributedText:(NSString *)source Font:(UIFont *)font Color:(UIColor *)color Offset:(CGFloat)offset{ 29 | if (!color) color = [UIColor blackColor]; 30 | if (!font) font = [UIFont systemFontOfSize:15]; 31 | 32 | NSRange prange = [source rangeOfString:@"["]; 33 | NSRange hrange = [source rangeOfString:@"]"]; 34 | if ((prange.location==NSNotFound)||(hrange.location==NSNotFound)) { // 没有自定义表情 35 | NSAttributedString * attributeString = [[NSAttributedString alloc] initWithString:source attributes:@{NSFontAttributeName:font,NSForegroundColorAttributeName:color}]; 36 | return attributeString; 37 | } 38 | 39 | // 有可能包含自定义表情 40 | NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] init]; 41 | NSScanner *theScanner = [NSScanner scannerWithString:source]; 42 | NSString *text = nil; 43 | while (![theScanner isAtEnd]) { 44 | //截取"["之前字符 45 | [theScanner scanUpToString:@"[" intoString:&text]; 46 | if (text&&(text.length > 0)) { 47 | NSAttributedString *str = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName:font,NSForegroundColorAttributeName:color}]; 48 | [attributeString appendAttributedString:str]; 49 | }//end 50 | 51 | text = nil; 52 | //取得"[]"之间字符 53 | [theScanner scanUpToString:@"]" intoString:&text]; 54 | if (text && ![text isEqualToString:@"]"] && (text.length > 0)) { 55 | NSString *imageName = [text substringFromIndex:1]; 56 | BOOL insert= [[self class]insertAttri:attributeString imageName:imageName font:font]; 57 | if (!insert) { 58 | NSString *txt = [NSString stringWithFormat:@"%@]", text]; 59 | NSDictionary *attrs = nil; 60 | // 识别是不是草稿 61 | if ([txt isEqualToString:@"[草稿]"]) { 62 | attrs = @{NSFontAttributeName:font, NSForegroundColorAttributeName:[UIColor redColor]}; 63 | } else { 64 | attrs = @{NSFontAttributeName:font, NSForegroundColorAttributeName:color}; 65 | } 66 | 67 | NSAttributedString * str = [[NSAttributedString alloc] initWithString:txt attributes:attrs]; 68 | if (str) { 69 | [attributeString appendAttributedString:str]; 70 | } 71 | 72 | } 73 | } 74 | text = nil; 75 | [theScanner scanString:@"]" intoString:NULL]; 76 | } 77 | return attributeString; 78 | } 79 | 80 | + (BOOL)insertAttri:(NSMutableAttributedString *)attri imageName:(NSString *)imageName font:(UIFont *)font{ 81 | NSData *data = UIImagePNGRepresentation([UIImage imageNamed:imageName]); 82 | YTTextAttachment *attach = [[YTTextAttachment alloc] initWithData:data ofType:nil]; 83 | if (attach.image && attach.image.size.width > 1.0f) { 84 | // 表情插入strat 85 | attach.emojiCode = [NSString stringWithFormat:@"[%@]", imageName]; 86 | [attach insertAttri:attri font:font]; 87 | return YES; 88 | // 表情图片插入end 89 | }else{ 90 | return NO; 91 | } 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/YTKeyBoardView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTKeyBoardView.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YTMoreView.h" 11 | #import "YTTextView.h" 12 | 13 | @class YTKeyBoardView; 14 | @protocol YTKeyBoardDelegate 15 | @optional 16 | // 当键盘位置发生变化是调用,durtaion时长 17 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard ChangeDuration:(CGFloat)durtaion; 18 | 19 | // 发送事件 文字,图片都有可能 20 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard sendResous:(id)resous; 21 | 22 | // 音频事件 23 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard audioRuning:(UILongPressGestureRecognizer *)longPress; 24 | 25 | // 其他事件,有可能是后期扩展 26 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard otherType:(YTMoreViewTypeAction)type; 27 | @end 28 | 29 | @interface YTKeyBoardView : UIView 30 | /* 键盘顶部view 控制键盘各种事件 */ 31 | @property (nonatomic,strong) UIView *topView; 32 | 33 | // 创建键盘快捷方法 34 | - (instancetype)initDelegate:(id)delegate superView:(UIView *)superView; 35 | 36 | // 点击,键盘回到底部 37 | - (void)tapAction; 38 | @end 39 | -------------------------------------------------------------------------------- /YTChatDemo/KeyBoard/YTKeyBoardView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTKeyBoardView.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "SLGrowingTextView.h" 10 | #import "YTKeyBoardView.h" 11 | #import "UIView+YTLayer.h" 12 | #import "UIImage+YTGif.h" 13 | #import "YTDeviceTest.h" 14 | #import "YTEmojiView.h" 15 | #import "YTEmoji.h" 16 | 17 | @interface YTSwitcherView : UIView 18 | 19 | @end 20 | 21 | @implementation YTSwitcherView 22 | #define DURTAION 0.25f 23 | - (void)addSubview:(UIView *)view{ 24 | // 动画添加子view 并且改控件只包含一个子view 25 | CGRect rect = self.frame; 26 | CGRect frame = view.frame; 27 | rect.size.height = CGRectGetHeight(frame); 28 | self.frame = rect; 29 | for (UIView * v in self.subviews) {// 移除前一个view 30 | [v removeFromSuperview]; 31 | } 32 | [super addSubview:view];// 添加一个view 33 | frame.origin.y = CGRectGetHeight(self.frame); 34 | view.frame = frame; 35 | frame.origin.y = 0; 36 | [UIView animateWithDuration:DURTAION animations:^{// 动画显示 37 | view.frame = frame; 38 | }]; 39 | } 40 | 41 | @end 42 | 43 | @interface YTKeyBoardView(){ 44 | /* topView 一些设置全局参数 */ 45 | CGFloat top_end_h; // textView 隐藏前的高度 46 | 47 | /* textView 一些设置全局参数 */ 48 | //CGFloat text_one_hight; // 一行文字高度 49 | NSUInteger text_location; // 将要插入表情时 记录最后光标位置 50 | BOOL text_beInsert; // 记录光标位置后 是否允许插入表情 51 | 52 | /* keyBoard 一些设置全局参数 */ 53 | BOOL kb_resign; //系统键盘已响应 响应为YES:且每次响应其值仅用一次 54 | BOOL kb_visiable; 55 | 56 | /* audio(音频) 一些设置全局参数 */ 57 | BOOL audio_beTap; //音频状态按钮是否已被点击 58 | } 59 | 60 | @property (nonatomic, strong) UIButton *audio; //录音图标 61 | @property (nonatomic, strong) UIButton *emoji; //表情图标 62 | @property (nonatomic, strong) UIButton *more; //更多图标“+” 63 | @property (nonatomic, strong) NSArray *icons; //图标集合 64 | 65 | @property (nonatomic, assign) id delegate; //代理 66 | @property (nonatomic, strong) SLGrowingTextView *textView; //输入框 67 | @property (nonatomic, strong) UIButton *audioBt; //音频录制开关 68 | @property (nonatomic, strong) UIView *bottomView; //底部各种切换控件 69 | 70 | @property (nonatomic, strong) YTEmojiView *emojiView; //表情控制器 71 | @property (nonatomic, strong) UIView *audioView; //录音控制器 72 | @property (nonatomic, strong) YTMoreView *moreView; 73 | 74 | @end 75 | 76 | @implementation YTKeyBoardView 77 | 78 | #define KB_WIDTH ([UIScreen mainScreen].bounds.size.width) 79 | /* 小图标(录音,表情,更多)位置参数 */ 80 | #define ICON_LR 12.0f //左右边距 81 | #define ICON_TOP 8.0f //顶端边距 82 | #define ICON_WH 28.0f //宽高 83 | /* textView 高度默认补充 为了显示更好看 */ 84 | #define TOP_H 44.0f 85 | #define TEXT_FIT 10.0f 86 | 87 | #pragma mark - init 初始化 88 | - (instancetype)initDelegate:(id)delegate superView:(UIView *)superView{ 89 | if (self = [super init]) { 90 | [self initUI]; 91 | [self addNotifations]; 92 | [self addToSuperView:superView]; 93 | self.delegate = delegate; 94 | } 95 | return self; 96 | } 97 | 98 | - (void)initUI{ 99 | [self addTopAndBottom]; 100 | [self addIcons]; 101 | [self addTextView]; 102 | //[self addAudionButton]; 103 | [self addContentView]; 104 | } 105 | 106 | - (void)addTopAndBottom{ 107 | UIView * top = [[UIView alloc]initWithFrame:CGRectMake( -1, 0,KB_WIDTH+2, TOP_H)]; 108 | top.backgroundColor = [UIColor whiteColor]; 109 | [top borderWithColor:[UIColor grayColor] borderWidth:0.5f]; 110 | [self addSubview:top]; 111 | self.topView = top; 112 | 113 | YTSwitcherView * bottom = [[YTSwitcherView alloc]initWithFrame:CGRectMake(0, CGRectGetHeight(top.frame), KB_WIDTH, EMOJI_VIEW_HEIGHT)]; 114 | bottom.backgroundColor = [UIColor clearColor]; 115 | [self addSubview:bottom]; 116 | self.bottomView = bottom; 117 | } 118 | 119 | - (void)addIcons{ 120 | UIButton *audio = [[UIButton alloc]initWithFrame:CGRectMake(ICON_LR, ICON_TOP, ICON_WH, ICON_WH)]; 121 | audio.tag = 1; 122 | UIButton *emoji = [[UIButton alloc]initWithFrame:CGRectMake(KB_WIDTH-(ICON_LR+ICON_WH)*2.0f, ICON_TOP, ICON_WH, ICON_WH)]; 123 | emoji.tag = 2; 124 | UIButton *more = [[UIButton alloc]initWithFrame:CGRectMake(KB_WIDTH-ICON_LR-ICON_WH, ICON_TOP, ICON_WH, ICON_WH)]; 125 | more.tag = 3; 126 | 127 | [audio setImage:[UIImage imageNamed:@"btn_say"] forState:UIControlStateNormal]; 128 | [emoji setImage:[UIImage imageNamed:@"btn_face"] forState:UIControlStateNormal]; 129 | [more setImage:[UIImage imageNamed:@"btn_more"] forState:UIControlStateNormal]; 130 | 131 | self.icons = @[emoji,more];// audio 132 | for (UIButton * bt in self.icons) { 133 | [bt setImage:[UIImage imageNamed:@"btn_key"] forState:UIControlStateSelected]; 134 | [bt addTarget:self action:@selector(iconsAction:) forControlEvents:UIControlEventTouchUpInside]; 135 | [self.topView addSubview:bt]; 136 | } 137 | //self.audio = audio; 138 | self.emoji = emoji; 139 | self.more = more; 140 | } 141 | 142 | - (void)addTextView{ 143 | SLGrowingTextView * text = [[SLGrowingTextView alloc]init]; 144 | text.delegate = self; 145 | text.returnKeyType = UIReturnKeySend; 146 | text.enablesReturnKeyAutomatically = YES; 147 | text.font = [UIFont systemFontOfSize:16.0f]; 148 | text.minNumberOfLines = 1; 149 | text.maxNumberOfLines = 5; 150 | text.backgroundColor = [UIColor whiteColor]; 151 | [text cornerRadius:5.0f borderColor:[UIColor grayColor] borderWidth:0.5f]; 152 | CGFloat hight = [text sizeThatFits:CGSizeMake(KB_WIDTH-ICON_WH*2.0f-ICON_LR*4.0f, CGFLOAT_MAX)].height; 153 | text.frame = CGRectMake(CGRectGetMaxX(self.audio.frame)+ICON_LR, ICON_TOP, KB_WIDTH-ICON_WH*2.0f-ICON_LR*4.0f, hight); 154 | CGFloat insetsTB = (TOP_H - ICON_TOP*2 - hight)*0.5; 155 | text.contentInset = UIEdgeInsetsMake(insetsTB, 2, insetsTB, 2); 156 | [text sizeToFit]; 157 | [self.topView addSubview:text]; 158 | self.textView = text; 159 | } 160 | 161 | - (void)addAudionButton{ 162 | UIButton *audioBt = [[UIButton alloc]initWithFrame:self.textView.frame]; 163 | audioBt.hidden = YES; 164 | audioBt.backgroundColor = [UIColor whiteColor]; 165 | [audioBt setTitle:@"按住说话" forState:UIControlStateNormal]; 166 | [audioBt setTitle:@"松开结束" forState:UIControlStateSelected]; 167 | [audioBt setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; 168 | [audioBt cornerRadius:5.0f borderColor:[UIColor grayColor] borderWidth:0.5f]; 169 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(audionRecord:)]; 170 | [audioBt addGestureRecognizer:longPress]; 171 | [self.topView addSubview:audioBt]; 172 | self.audioBt = audioBt; 173 | } 174 | 175 | - (void)addContentView{ 176 | //表情 177 | self.emojiView = [[YTEmojiView alloc]initWithDelegate:self]; 178 | //音频 179 | self.audioView = [[UIView alloc]init]; 180 | //更多 181 | self.moreView = [[YTMoreView alloc]initWithFrame:self.emojiView.bounds]; 182 | self.moreView.delegate = self; 183 | } 184 | 185 | - (void)addNotifations{ 186 | [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyBoardHiden:) name:UIKeyboardWillHideNotification object:nil]; 187 | [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyBoardShow:) name:UIKeyboardWillShowNotification object:nil]; 188 | } 189 | 190 | - (void)addToSuperView:(UIView *)superView{ 191 | CGFloat H = CGRectGetHeight(superView.bounds); 192 | CGFloat topH = self.topView.bounds.size.height; 193 | CGRect frame = CGRectMake(0,H-topH,KB_WIDTH, H); 194 | self.frame = frame; 195 | [superView addSubview:self]; 196 | } 197 | 198 | #pragma mark - Key Board icons action 199 | - (void)iconsAction:(UIButton*)sender{ 200 | [self audionDispose:NO]; 201 | if (sender.selected) { 202 | [self.textView becomeFirstResponder]; 203 | return; 204 | }else{ 205 | kb_resign = YES; 206 | [self.textView resignFirstResponder]; 207 | 208 | for (UIButton * b in self.icons) { 209 | if ([b isEqual:sender]) { 210 | sender.selected = !sender.selected; 211 | }else{ 212 | b.selected = NO; 213 | } 214 | } 215 | UIView * visiableView = nil; 216 | switch (sender.tag) { 217 | case 1://录音 218 | { 219 | visiableView = self.audioView; 220 | [self audionDispose:YES]; 221 | break; 222 | } 223 | case 2://表情 224 | { 225 | visiableView = self.emojiView; 226 | text_location = self.textView.selectedRange.location; 227 | text_beInsert = YES; 228 | break; 229 | } 230 | case 3://其他+ 231 | { 232 | visiableView = self.moreView; 233 | break; 234 | } 235 | default: 236 | visiableView = [[UIView alloc]init]; 237 | break; 238 | } 239 | [self.bottomView addSubview:visiableView]; 240 | CGRect fram = self.frame; 241 | fram.origin.y =[UIScreen mainScreen].bounds.size.height- (CGRectGetHeight(visiableView.frame) + self.topView.bounds.size.height); 242 | [self duration:DURTAION EndF:fram Options:UIViewAnimationOptionCurveLinear]; 243 | } 244 | } 245 | 246 | - (void)audionDispose:(BOOL)tap{ 247 | if (tap) { 248 | audio_beTap = YES; 249 | self.textView.hidden = YES; 250 | self.audioBt.hidden = NO; 251 | CGRect frame = self.textView.frame; 252 | frame.size.height = 27.0f; 253 | self.audioBt.frame = frame; 254 | frame = self.topView.frame; 255 | top_end_h = frame.size.height; 256 | frame.size.height = TOP_H; 257 | self.topView.frame = frame; 258 | frame = self.frame; 259 | frame.origin.y += (top_end_h - TOP_H); 260 | [self duration:0 EndF:frame Options:UIViewAnimationOptionCurveLinear]; 261 | }else{ 262 | if (audio_beTap==NO) return; 263 | audio_beTap = NO; 264 | self.textView.hidden = NO; 265 | self.audioBt.hidden = YES; 266 | CGRect frame = self.topView.frame; 267 | frame.size.height = top_end_h; 268 | self.topView.frame = frame; 269 | frame = self.frame; 270 | frame.origin.y -= (top_end_h - TOP_H); 271 | [self duration:0 EndF:frame Options:UIViewAnimationOptionCurveLinear]; 272 | } 273 | 274 | } 275 | 276 | #pragma mark - 系统键盘通知事件 277 | - (void)keyBoardHiden:(NSNotification*)noti{ 278 | if (kb_resign==NO) { 279 | CGRect endF = [[noti.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]CGRectValue]; 280 | CGFloat duration = [[noti.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; 281 | UIViewAnimationOptions options = [[noti.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; 282 | CGRect fram = self.frame; 283 | fram.origin.y = (endF.origin.y - _topView.frame.size.height); 284 | [self duration:duration EndF:fram Options:options]; 285 | }else{ 286 | kb_resign = NO; 287 | } 288 | } 289 | 290 | - (void)keyBoardShow:(NSNotification*)noti{ 291 | CGRect endF = [[noti.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]CGRectValue]; 292 | if (kb_resign==NO) { 293 | for (UIButton * b in self.icons) { 294 | b.selected = NO; 295 | } 296 | [self.bottomView addSubview:[UIView new]]; 297 | 298 | NSTimeInterval duration = [[noti.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; 299 | UIViewAnimationOptions options = [[noti.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; 300 | CGRect fram = self.frame; 301 | fram.origin.y = (endF.origin.y - _topView.frame.size.height); 302 | [self duration:duration EndF:fram Options:options]; 303 | }else{ 304 | kb_resign = NO; 305 | } 306 | } 307 | 308 | #pragma mark - chat Emoji View Delegate 309 | - (void)emojiViewEmoji:(YTEmoji *)emoji{ 310 | if (!emoji.emojiCode||(emoji.emojiCode.length == 0)) { 311 | UIImage *image = [UIImage gifImageNamed:emoji.emojiImage]; 312 | [self sendResous:image]; 313 | }else{ 314 | NSMutableAttributedString *attributeString = nil; 315 | NSAttributedString *attribu = nil; 316 | if (text_beInsert) { 317 | NSRange range = NSMakeRange(0, text_location); 318 | attributeString = [[NSMutableAttributedString alloc]initWithAttributedString:[self.textView.internalTextView.attributedText attributedSubstringFromRange:range]]; 319 | range = NSMakeRange(text_location, self.textView.internalTextView.attributedText.length-text_location); 320 | attribu = [[self.textView.internalTextView.attributedText attributedSubstringFromRange:range] mutableCopy]; 321 | text_beInsert = NO; 322 | }else{ 323 | attributeString = [[NSMutableAttributedString alloc]initWithAttributedString:self.textView.internalTextView.attributedText]; 324 | } 325 | 326 | BOOL insert = [YTTextView insertAttri:attributeString imageName:emoji.emojiImage font:self.textView.font]; 327 | if (attribu) { 328 | [attributeString appendAttributedString:attribu]; 329 | } 330 | if (insert) { 331 | self.textView.internalTextView.attributedText = attributeString; 332 | } 333 | } 334 | } 335 | 336 | - (void)emojiViewDelete{ 337 | NSRange range = self.textView.selectedRange; 338 | NSInteger location = (NSInteger)range.location; 339 | if (location == 0) { 340 | return; 341 | } 342 | range.location = location-1; 343 | range.length = 1; 344 | 345 | NSMutableAttributedString *attStr = [self.textView.internalTextView.attributedText mutableCopy]; 346 | [attStr deleteCharactersInRange:range]; 347 | self.textView.internalTextView.attributedText = attStr; 348 | self.textView.selectedRange = range; 349 | } 350 | 351 | - (void)emojiViewSend{ 352 | [self sendMessage]; 353 | } 354 | 355 | #pragma mark - text View Delegate 356 | - (BOOL)growingTextView:(SLGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{ 357 | if(![growingTextView hasText] && [text isEqualToString:@""]) { 358 | return NO; 359 | } 360 | if ([text isEqualToString:@"\n"]) { 361 | [self sendMessage]; 362 | return NO; 363 | } 364 | return YES; 365 | } 366 | 367 | - (void)growingTextView:(SLGrowingTextView *)growingTextView shouldChangeHeight:(CGFloat)height{ 368 | CGRect frame = self.textView.frame; 369 | frame.size.height = height; 370 | [UIView animateWithDuration:DURTAION animations:^{ 371 | self.textView.frame = frame; 372 | [self topLayoutSubViewWithH:(frame.size.height+ICON_TOP*2)]; 373 | }]; 374 | } 375 | 376 | -(void)audionRecord:(UILongPressGestureRecognizer*)longPress{ 377 | switch (longPress.state) { 378 | case UIGestureRecognizerStateBegan: 379 | { 380 | if ([YTDeviceTest userAuthorizationAudioStatus]) { 381 | self.audioBt.selected = YES; 382 | [self.audioBt setBackgroundColor:[UIColor lightGrayColor]]; 383 | for (UIButton * b in self.icons) { 384 | b.enabled = NO; 385 | } 386 | } 387 | break; 388 | } 389 | case UIGestureRecognizerStateEnded: 390 | case UIGestureRecognizerStateCancelled: 391 | { 392 | self.audioBt.selected = NO; 393 | [self.audioBt setBackgroundColor:[UIColor whiteColor]]; 394 | for (UIButton * b in self.icons) { 395 | b.enabled = YES; 396 | } 397 | break; 398 | } 399 | default: 400 | break; 401 | } 402 | [self audioRuning:longPress]; 403 | } 404 | 405 | #pragma mark - other logic 406 | - (void)topLayoutSubViewWithH:(CGFloat)hight{ 407 | CGRect frame = self.topView.frame; 408 | CGFloat diff = hight - frame.size.height; 409 | frame.size.height = hight; 410 | self.topView.frame = frame; 411 | 412 | frame = self.bottomView.frame; 413 | frame.origin.y = CGRectGetHeight(self.topView.bounds); 414 | self.bottomView.frame = frame; 415 | 416 | frame = self.frame; 417 | frame.origin.y -= diff; 418 | 419 | [self duration:DURTAION EndF:frame Options:UIViewAnimationOptionCurveLinear]; 420 | } 421 | 422 | - (void)duration:(CGFloat)duration EndF:(CGRect)endF Options:(UIViewAnimationOptions)options{ 423 | 424 | [UIView animateWithDuration:duration delay:0.0f options:options animations:^{ 425 | kb_resign = NO; 426 | self.frame = endF; 427 | } completion:^(BOOL finished) { 428 | 429 | }]; 430 | [self changeDuration:duration]; 431 | } 432 | 433 | - (void)sendMessage{ 434 | if (![self.textView hasText]&&(self.textView.text.length==0)) { 435 | return; 436 | } 437 | NSString *plainText = self.textView.internalTextView.clearText; 438 | //空格处理 439 | plainText = [plainText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 440 | 441 | if (plainText.length > 0) { 442 | [self sendResous:plainText]; 443 | self.textView.text = @""; 444 | } 445 | } 446 | 447 | #pragma mark - self public api action 448 | - (void)tapAction{ 449 | if (![self.textView isFirstResponder]) { 450 | UIButton * b = [[UIButton alloc]init]; 451 | b.selected = NO; 452 | [self iconsAction:b]; 453 | }else{ 454 | [self.textView resignFirstResponder]; 455 | } 456 | } 457 | 458 | #pragma mark - self delegate action 459 | - (void)changeDuration:(CGFloat)duration{ 460 | if ([self.delegate respondsToSelector:@selector(keyBoardView:ChangeDuration:)]) { 461 | [self.delegate keyBoardView:self ChangeDuration:duration]; 462 | } 463 | } 464 | 465 | - (void)sendResous:(id)resous{ 466 | if ([self.delegate respondsToSelector:@selector(keyBoardView:sendResous:)]) { 467 | [self.delegate keyBoardView:self sendResous:resous]; 468 | } 469 | } 470 | 471 | - (void)audioRuning:(UILongPressGestureRecognizer*)longPress{ 472 | if ([self.delegate respondsToSelector:@selector(keyBoardView:audioRuning:)]) { 473 | [self.delegate keyBoardView:self audioRuning:longPress]; 474 | } 475 | } 476 | 477 | - (void)moreViewType:(YTMoreViewTypeAction)type{ 478 | if ([self.delegate respondsToSelector:@selector(keyBoardView:otherType:)]) { 479 | [self.delegate keyBoardView:self otherType:type]; 480 | } 481 | } 482 | 483 | - (void)dealloc{ 484 | [[NSNotificationCenter defaultCenter]removeObserver:self]; 485 | } 486 | 487 | @end 488 | -------------------------------------------------------------------------------- /YTChatDemo/Main/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | 16 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 17 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 18 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 19 | 20 | - (void)saveContext; 21 | - (NSURL *)applicationDocumentsDirectory; 22 | 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /YTChatDemo/Main/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "YTCoreData.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | [YTCoreData instance];//初始化coreData 建立数据库 22 | return YES; 23 | } 24 | 25 | /* 26 | - (void)applicationWillResignActive:(UIApplication *)application { 27 | 28 | } 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | 32 | } 33 | 34 | - (void)applicationWillEnterForeground:(UIApplication *)application { 35 | 36 | } 37 | 38 | - (void)applicationDidBecomeActive:(UIApplication *)application { 39 | 40 | } 41 | 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application { 44 | [self saveContext]; 45 | } 46 | */ 47 | - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application{ 48 | /* 内存警告 */ 49 | [[NSURLCache sharedURLCache] removeAllCachedResponses]; 50 | } 51 | 52 | #pragma mark - Core Data stack & Saving support 53 | - (NSURL *)applicationDocumentsDirectory { 54 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 55 | } 56 | 57 | - (void)saveContext { 58 | NSError * error; 59 | if ([[YTCoreData instance].privateObjectContext hasChanges]) { 60 | [[YTCoreData instance].privateObjectContext save:&error]; 61 | } 62 | } 63 | 64 | 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /YTChatDemo/Main/StartVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // StartVC.h 3 | // ChatDemo 4 | // 5 | // Created by TI on 15/6/5. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface StartVC : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YTChatDemo/Main/StartVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // StartVC.m 3 | // ChatDemo 4 | // 5 | // Created by TI on 15/6/5. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "NSManagedObject+YTMoreThread.h" 10 | #import "YTImgBrowerController.h" 11 | #import "YTKeyBoardView.h" 12 | #import "YTDeviceTest.h" 13 | #import "YTImgInfo.h" 14 | #import "Defines.h" 15 | #import "StartVC.h" 16 | #import "Message.h" 17 | @import AssetsLibrary; 18 | 19 | @interface StartVC () 20 | { 21 | BOOL typeChange; 22 | } 23 | @property (nonatomic, strong) UITableView *tableView; 24 | @property (nonatomic, strong) YTKeyBoardView *keyBoard; 25 | @property (nonatomic, strong) NSMutableArray *infoArray; 26 | 27 | @property (nonatomic, strong) YTImgBrowerController *ImgBrower; 28 | @end 29 | 30 | @implementation StartVC 31 | 32 | - (NSMutableArray *)infoArray{ 33 | if (!_infoArray) { 34 | _infoArray = [[Message filter:nil orderby:@[@"objID"] offset:0 limit:0] mutableCopy]; 35 | } 36 | return _infoArray; 37 | } 38 | 39 | - (void)viewDidAppear:(BOOL)animated{ 40 | [super viewDidAppear:animated]; 41 | if (self.infoArray.count == 0) return; 42 | [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.infoArray.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:NO]; 43 | typeChange = ((Message *)self.infoArray.lastObject).type.boolValue; 44 | } 45 | 46 | - (void)viewDidLoad { 47 | [super viewDidLoad]; 48 | self.keyBoard = [[YTKeyBoardView alloc] 49 | initDelegate:self 50 | superView:self.view]; 51 | [self initUI]; 52 | } 53 | 54 | - (void)initUI{ 55 | [self addTableView]; 56 | UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] 57 | initWithTarget:self 58 | action:@selector(tapAction)]; 59 | [self.tableView addGestureRecognizer:tap]; 60 | 61 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"清空" style:UIBarButtonItemStyleDone target:self action:@selector(clearDataSource)]; 62 | 63 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"浏览相册" style:UIBarButtonItemStyleDone target:self action:@selector(seePhoto)]; 64 | } 65 | 66 | - (void)addTableView{ 67 | UITableView *tableView = [[UITableView alloc] 68 | initWithFrame:[self tableViewFrame]]; 69 | tableView.delegate = self; 70 | tableView.dataSource = self; 71 | tableView.tableFooterView = [UIView new]; 72 | tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 73 | [self.view insertSubview:(self.tableView = tableView) belowSubview:self.keyBoard]; 74 | } 75 | 76 | #pragma mark - key board delegate 77 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard ChangeDuration:(CGFloat)durtaion{ 78 | if (self.infoArray.count == 0) return; 79 | [UIView animateWithDuration:durtaion animations:^{ 80 | self.tableView.frame = [self tableViewFrame]; 81 | [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.infoArray.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:NO]; 82 | }]; 83 | } 84 | 85 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard audioRuning:(UILongPressGestureRecognizer *)longPress{ 86 | LOG(@"录音 -> 在此处理"); 87 | if (longPress.state == UIGestureRecognizerStateEnded) { 88 | [self keyBoardView:keyBoard sendResous:@"录音"]; 89 | } 90 | } 91 | 92 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard otherType:(YTMoreViewTypeAction)type{ 93 | LOG(@"相机 相册 -> 在此处理"); 94 | NSString * m; 95 | if (type == YTMoreViewTypeActionCamera) { 96 | m = @"相机"; 97 | }else{ 98 | m = @"相册"; 99 | } 100 | [self keyBoardView:keyBoard sendResous:m]; 101 | } 102 | 103 | - (void)keyBoardView:(YTKeyBoardView *)keyBoard sendResous:(id)resous{ 104 | if (!resous) return; 105 | NSString *string = @""; 106 | if ([resous isKindOfClass:[NSString class]]) { 107 | string = resous; 108 | }else if ([resous isKindOfClass:[UIImage class]]){ 109 | UIImage *image = resous; 110 | CGRect rect = CGRectZero; 111 | rect.size = image.size; 112 | UIView *view = [[UIView alloc]initWithFrame:rect]; 113 | rect.origin.x = (self.view.bounds.size.width - rect.size.width)*0.5f; 114 | UIImageView *imageView = [[UIImageView alloc]initWithFrame:rect]; 115 | imageView.image = image; 116 | [view addSubview:imageView]; 117 | self.tableView.tableFooterView = view; 118 | string = [NSString stringWithFormat:@"image:size->%@ ⬇️ 有图片展示",NSStringFromCGSize(image.size)]; 119 | }else{ 120 | return; 121 | } 122 | 123 | Message * m = [Message createNew]; 124 | m.content = string; 125 | m.type = (typeChange = !typeChange)?@(MessageTypeMe):@(MessageTypeOther); 126 | if (self.infoArray.count > 0) { 127 | m.objID = @(((Message*)[self.infoArray lastObject]).objID.intValue); 128 | }else{ 129 | m.objID = @(0); 130 | } 131 | 132 | [Message save:^(NSError *error) { 133 | [self endSend:m]; 134 | }]; 135 | 136 | } 137 | 138 | #pragma mark - tableView delegate & dataSource 139 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 140 | return self.infoArray.count; 141 | } 142 | 143 | static NSString * cellID = @"cellID"; 144 | - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 145 | UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID]; 146 | if (!cell) { 147 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; 148 | } 149 | Message * m = (Message*)self.infoArray[indexPath.row]; 150 | NSString * string = [NSString stringWithFormat:@"%@: %@",m.type.intValue == MessageTypeMe?@"大师兄":@"小师妹" , m.content]; 151 | UIColor *color = m.type.intValue == MessageTypeMe?[UIColor redColor]:[UIColor blackColor]; 152 | cell.textLabel.attributedText = [YTTextView getAttributedText:string Font:nil Color:color Offset:-3]; 153 | return cell; 154 | } 155 | 156 | #pragma mark - self action 157 | - (void)tapAction{ 158 | [self.keyBoard tapAction]; 159 | } 160 | 161 | - (CGRect)tableViewFrame{ 162 | CGRect frame = self.view.bounds; 163 | if (!self.navigationController) { 164 | frame.origin.y = [UIApplication sharedApplication].statusBarFrame.size.height; 165 | } 166 | frame.size.height = self.keyBoard.frame.origin.y-frame.origin.y; 167 | return frame; 168 | } 169 | 170 | - (void)endSend:(Message*)message{ 171 | [self.infoArray addObject:message]; 172 | [self.tableView beginUpdates]; 173 | NSIndexPath * path = [NSIndexPath indexPathForRow:(self.infoArray.count - 1) inSection:0]; 174 | [self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationFade]; 175 | [self.tableView endUpdates]; 176 | 177 | [self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionBottom animated:YES]; 178 | } 179 | 180 | - (void)clearDataSource{ 181 | if (self.infoArray.count == 0) return; 182 | for (Message * m in self.infoArray) { 183 | [Message delobject:m]; 184 | } 185 | [Message save:^(NSError *error) { 186 | [self.infoArray removeAllObjects]; 187 | dispatch_async(dispatch_get_main_queue(), ^{ 188 | [self.tableView reloadData]; 189 | }); 190 | }]; 191 | } 192 | 193 | //------------------------------------------------------------- 194 | /* 195 | 在这里仅仅是微弱的展示ACDsee的用法,时间原因,后面打算写一个类似微信的相册发 196 | 图片功能,不过核心功能已经完成,只是一些零碎的搭建和对ACDsee二次封装还没进行 197 | 有兴趣的朋友也可看下这个https://github.com/TiYcc/ImageBrower.git是我 198 | 写的,仅仅作为了解,还是值得一看的。 199 | */ 200 | - (void)seePhoto{ 201 | self.navigationItem.leftBarButtonItem.enabled = NO; 202 | NSMutableArray *assets = [NSMutableArray array]; 203 | ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc]init]; 204 | [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { 205 | if (group) { 206 | [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { 207 | if (result) { 208 | [assets addObject:result]; 209 | } 210 | }]; 211 | }else{ 212 | if (self.ImgBrower) { 213 | self.ImgBrower = nil; 214 | } 215 | NSArray *imgInfos = [YTImgInfo imgInfosWithImgs:nil urls:assets type:YTImgInfoFromeTypePhoto]; 216 | self.ImgBrower = [[YTImgBrowerController alloc]initWithDelegate:self imgInfos:imgInfos index:0]; 217 | return ; 218 | } 219 | } failureBlock:^(NSError *error) { 220 | 221 | }]; 222 | } 223 | 224 | - (void)imgBrowerControllerInitEnd:(YTImgInfo *)imgInfo{ 225 | [self presentViewController:self.ImgBrower animated:YES completion:^{ 226 | self.navigationItem.leftBarButtonItem.enabled = YES; 227 | }]; 228 | } 229 | 230 | - (void)imgBrowerControllerWillDismiss:(YTImgInfo *)imgInfo{ 231 | self.tableView.backgroundColor = [UIColor whiteColor]; 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Defines.h: -------------------------------------------------------------------------------- 1 | // 2 | // Defines.h 3 | // ChatDemo 4 | // 5 | // Created by TI on 15/6/8. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #ifdef DEBUG 10 | 11 | #define log(format,...) NSLog(format, ## __VA_ARGS__) 12 | #define LOG(format) log(@"%@",(format)) 13 | 14 | #else 15 | 16 | #define log(format,...) 17 | #define LOG(format) 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "40x40", 15 | "idiom" : "iphone", 16 | "filename" : "YT@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "idiom" : "iphone", 21 | "size" : "40x40", 22 | "scale" : "3x" 23 | }, 24 | { 25 | "idiom" : "iphone", 26 | "size" : "60x60", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "idiom" : "iphone", 31 | "size" : "60x60", 32 | "scale" : "3x" 33 | } 34 | ], 35 | "info" : { 36 | "version" : 1, 37 | "author" : "xcode" 38 | } 39 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/AppIcon.appiconset/YT@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/AppIcon.appiconset/YT@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_del.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_facecancel@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_del.imageset/btn_facecancel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_del.imageset/btn_facecancel@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_face.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_face@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "btn_face@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_face.imageset/btn_face@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_face.imageset/btn_face@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_face.imageset/btn_face@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_face.imageset/btn_face@3x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_key.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_key@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "btn_key@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_key.imageset/btn_key@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_key.imageset/btn_key@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_key.imageset/btn_key@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_key.imageset/btn_key@3x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_chatmore@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "btn_chatmore@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_more.imageset/btn_chatmore@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_more.imageset/btn_chatmore@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_more.imageset/btn_chatmore@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_more.imageset/btn_chatmore@3x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_photo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_photo@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "btn_photo@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_photo.imageset/btn_photo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_photo.imageset/btn_photo@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_photo.imageset/btn_photo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_photo.imageset/btn_photo@3x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_pic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_pic@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "btn_pic@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_pic.imageset/btn_pic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_pic.imageset/btn_pic@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_pic.imageset/btn_pic@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_pic.imageset/btn_pic@3x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_say.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "btn_say@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "btn_say@3x.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_say.imageset/btn_say@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_say.imageset/btn_say@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/btn_say.imageset/btn_say@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/btn_say.imageset/btn_say@3x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/camera_down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "camera_down@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/camera_down.imageset/camera_down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/camera_down.imageset/camera_down@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/camera_nomal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "camera@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/camera_nomal.imageset/camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/camera_nomal.imageset/camera@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/default_img.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "default_img@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/default_img.imageset/default_img@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/default_img.imageset/default_img@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/photo_down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "img_down@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/photo_down.imageset/img_down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/photo_down.imageset/img_down@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/photo_nomal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "img@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Images.xcassets/photo_nomal.imageset/img@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiYcc/KeyBoard/dfcec9a85580fcff301d4fa832c1de1c74566320/YTChatDemo/Supporting Files/Images.xcassets/photo_nomal.imageset/img@2x.png -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | zh_CN 7 | CFBundleDisplayName 8 | YT 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | com.YccTime.app.$(PRODUCT_NAME:rfc1034identifier) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 1.1.0 29 | LSRequiresIPhoneOS 30 | 31 | UILaunchStoryboardName 32 | Main 33 | UIMainStoryboardFile 34 | Main 35 | UIRequiredDeviceCapabilities 36 | 37 | armv7 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Message.h: -------------------------------------------------------------------------------- 1 | // 2 | // Message.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/2. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSInteger, MessageType) { 13 | MessageTypeMe = 0, 14 | MessageTypeOther 15 | }; 16 | 17 | @interface Message : NSManagedObject 18 | 19 | @property (nonatomic, retain) NSString * content; 20 | @property (nonatomic, retain) NSNumber * objID; 21 | @property (nonatomic, retain) NSNumber * type; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/Message.m: -------------------------------------------------------------------------------- 1 | // 2 | // Message.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/2. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "Message.h" 10 | 11 | 12 | @implementation Message 13 | 14 | @dynamic content; 15 | @dynamic objID; 16 | @dynamic type; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/YTChatDemo.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | YTChatDemo.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/YTChatDemo.xcdatamodeld/YTChatDemo.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /YTChatDemo/Supporting Files/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. 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 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/NSDictionary+YTSafe.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+YTSafe.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSDictionary (YTSafe) 12 | /* 顾名思义,不解释 */ 13 | - (id)safeValueForKey:(NSString *)key; 14 | - (NSDictionary*)safeDictForKey:(NSString *)key; 15 | - (NSArray*)safeArrayForKey:(NSString *)key; 16 | - (NSInteger)safeIntegerValueForKey:(NSString *)key; 17 | - (float)safeFloatValueForKey:(NSString *)key; 18 | - (NSString*)safeStringValueForKey:(NSString *)key; 19 | - (BOOL)safeBoolValueForKey:(NSString*)key; 20 | @end 21 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/NSDictionary+YTSafe.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+YTSafe.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/25. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "NSDictionary+YTSafe.h" 10 | 11 | @implementation NSDictionary (YTSafe) 12 | 13 | - (id)safeValueForKey:(NSString *)key { 14 | id obj = [self objectForKey:key]; 15 | if([self isEmpty:obj]) { 16 | return @""; 17 | } else if([obj isKindOfClass:[NSNumber class]]) { 18 | return [(NSNumber *)obj stringValue]; 19 | } 20 | return obj; 21 | } 22 | 23 | - (NSDictionary *)safeDictForKey:(NSString *)key { 24 | id obj = [self objectForKey:key]; 25 | if([self isEmpty:obj]) { 26 | return @{}; 27 | } else if(![obj isKindOfClass:[NSDictionary class]]) { 28 | return @{}; 29 | } 30 | return obj; 31 | } 32 | 33 | - (NSArray *)safeArrayForKey:(NSString *)key { 34 | id obj = [self objectForKey:key]; 35 | if([self isEmpty:obj]) { 36 | return @[]; 37 | }else if(![obj isKindOfClass:[NSArray class]]) { 38 | return @[]; 39 | } 40 | return obj; 41 | } 42 | 43 | - (NSInteger)safeIntegerValueForKey:(NSString *)key { 44 | id obj = [self objectForKey:key]; 45 | if ([self isEmpty:obj]) { 46 | return 0; 47 | }else if (![obj isKindOfClass:[NSNumber class]]) { 48 | if ([obj isKindOfClass:[NSString class]]) { 49 | return [obj integerValue]; 50 | } else { 51 | return 0; 52 | } 53 | } 54 | return [obj integerValue]; 55 | } 56 | 57 | - (float)safeFloatValueForKey:(NSString *)key { 58 | id obj = [self objectForKey:key]; 59 | if ([self isEmpty:obj]) { 60 | return 0.0f; 61 | }else if (![obj isKindOfClass:[NSNumber class]]) { 62 | if ([obj isKindOfClass:[NSString class]]) { 63 | return [obj floatValue]; 64 | } else { 65 | return 0.0f; 66 | } 67 | } 68 | return [obj floatValue]; 69 | } 70 | 71 | - (NSString *)safeStringValueForKey:(NSString *)key { 72 | id obj = [self objectForKey:key]; 73 | if ([self isEmpty:obj]) { 74 | return @""; 75 | } 76 | if ([obj isKindOfClass:[NSNumber class]]) { 77 | return [obj stringValue]; 78 | } 79 | if (![obj isKindOfClass:[NSString class]]) { 80 | return @""; 81 | } 82 | if ([@"null" isEqualToString:obj]) { 83 | return @""; 84 | } 85 | return obj; 86 | } 87 | 88 | - (BOOL)safeBoolValueForKey:(NSString *)key{ 89 | id obj = [self objectForKey:key]; 90 | if ([self isEmpty:obj]) { 91 | return NO; 92 | } 93 | if (![obj isKindOfClass:[NSNumber class]]) { 94 | if ([obj isKindOfClass:[NSString class]]) { 95 | return [obj boolValue]; 96 | } else { 97 | return NO; 98 | } 99 | } 100 | return [obj boolValue]; 101 | } 102 | 103 | - (BOOL)isEmpty:(id)obj{ 104 | return ((!obj)||([obj isKindOfClass:[NSNull class]])); 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIColor+YTBacal.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+YTBacal.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIColor (YTBacal) 12 | 13 | + (UIColor*)colorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b A:(CGFloat)a; 14 | 15 | + (UIColor*)colorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b; 16 | 17 | //#95a5a6灰 18 | + (UIColor *)C_95A5A6; 19 | 20 | //#ecf0f1白雾色 21 | + (UIColor *)C_ECF0F1; 22 | 23 | //#dddddd浅灰 24 | + (UIColor *)C_DDDDDD; 25 | 26 | //#a9a9a9深灰 27 | + (UIColor *)C_A9A9A9; 28 | 29 | //#fcfcfc浅白 30 | + (UIColor *)C_FCFCFC; 31 | 32 | //#f9f7f7灰白 33 | + (UIColor *)C_F9F7F9; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIColor+YTBacal.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+YTBacal.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "UIColor+YTBacal.h" 10 | 11 | @implementation UIColor (YTBacal) 12 | 13 | + (UIColor *)colorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b A:(CGFloat)a{ 14 | return [self colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a]; 15 | } 16 | 17 | + (UIColor *)colorR:(CGFloat)r G:(CGFloat)g B:(CGFloat)b{ 18 | return [self colorR:r G:g B:b A:1.0f]; 19 | } 20 | 21 | + (UIColor *)C_95A5A6{//灰 22 | return [self colorR:149 G:165 B:166]; 23 | } 24 | 25 | + (UIColor *)C_ECF0F1{//白雾色 26 | return [self colorR:236 G:240 B:241]; 27 | } 28 | 29 | + (UIColor *)C_DDDDDD{//浅灰 30 | return [self colorR:221 G:221 B:221]; 31 | } 32 | 33 | + (UIColor *)C_A9A9A9{//深灰 34 | return [self colorR:169 G:169 B:169]; 35 | } 36 | 37 | + (UIColor *)C_FCFCFC{//浅白 38 | return [self colorR:252 G:252 B:252]; 39 | } 40 | 41 | + (UIColor *)C_F9F7F9{//灰白 42 | return [self colorR:249 G:247 B:247]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIColor+YTKeyBoard.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+YTKeyBoard.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIColor+YTBacal.h" 11 | 12 | @interface UIColor (YTKeyBoard) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIColor+YTKeyBoard.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+YTKeyBoard.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/31. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "UIColor+YTKeyBoard.h" 10 | 11 | @implementation UIColor (YTKeyBoard) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIImage+YTGif.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+YTGif.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/6. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (YTGif) 12 | /** 根据名字去取gif图片,有可能返回PNG类型image及nil */ 13 | + (UIImage *)gifImageNamed:(NSString *)name; 14 | 15 | /** 根据data去取gif图片,有可能返回PNG类型image及nil */ 16 | + (UIImage *)gifImageWithData:(NSData *)data; 17 | @end 18 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIImage+YTGif.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+YTGif.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/6. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "UIImage+YTGif.h" 10 | @import ImageIO; 11 | 12 | @implementation UIImage (YTGif) 13 | 14 | + (UIImage *)gifImageNamed:(NSString *)name{ 15 | NSString *gifPath = [[NSBundle mainBundle] pathForResource:[name stringByAppendingString:@"@2x"] ofType:@"gif"]; 16 | if (!gifPath) { 17 | gifPath = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"]; 18 | } 19 | NSData *gifData = [NSData dataWithContentsOfFile:gifPath]; 20 | if (gifData) { 21 | return [self gifImageWithData:gifData]; 22 | }else { 23 | return [self imageNamed:name]; 24 | } 25 | } 26 | 27 | + (UIImage *)gifImageWithData:(NSData *)data{ 28 | if (!data) return nil; 29 | CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); 30 | size_t count = CGImageSourceGetCount(source); 31 | UIImage *gifImage = nil; 32 | if (count <= 1) { 33 | gifImage = [self imageWithData:data]; 34 | }else{ 35 | NSMutableArray *images = [NSMutableArray arrayWithCapacity:count]; 36 | for (size_t index = 0; index < count; index++) { 37 | CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, index, NULL); 38 | [images addObject:[UIImage imageWithCGImage:imageRef]]; 39 | CGImageRelease(imageRef); 40 | } 41 | CGFloat delayTime = [self durtaionAtIndex:0 source:source]; 42 | NSTimeInterval duration = delayTime*count; 43 | gifImage = [self animatedImageWithImages:images duration:duration]; 44 | } 45 | CFRelease(source); 46 | return gifImage; 47 | } 48 | 49 | // 取出指定位置图片播放时长 50 | + (CGFloat)durtaionAtIndex:(size_t)index source:(CGImageSourceRef)source{ 51 | CGFloat dealyTime = 0.0f; 52 | CFDictionaryRef sourceProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); 53 | NSDictionary *dicProperties = (__bridge NSDictionary *)sourceProperties; 54 | NSDictionary *gifProperties = dicProperties[(NSString *)kCGImagePropertyGIFDictionary]; 55 | NSNumber *delayTimeUnclamped = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; 56 | 57 | if (delayTimeUnclamped) { 58 | dealyTime = [delayTimeUnclamped floatValue]; 59 | }else { 60 | NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; 61 | if (dealyTime) { 62 | dealyTime = [delayTimeProp floatValue]; 63 | } 64 | } 65 | if (dealyTime <= 0.01) dealyTime = 0.1f; 66 | CFRelease(sourceProperties); 67 | return dealyTime; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIView+YTLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+YTLayer.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (YTLayer) 12 | 13 | /* 指定圆角大小处理 */ 14 | - (void)cornerRadius:(CGFloat)radius; 15 | 16 | /* 添加border */ 17 | - (void)borderWithColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth; 18 | 19 | /* 指定圆角大小,且带border */ 20 | - (void)cornerRadius:(CGFloat)radius borderColor:(UIColor*)borderColor borderWidth:(CGFloat)borderWidth; 21 | 22 | /* 对UIView的四个角进行选择性的圆角处理 */ 23 | - (void)makeRoundedCorner:(UIRectCorner)byRoundingCorners cornerRadii:(CGSize)cornerRadii; 24 | @end 25 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/Catgray/UIView+YTLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+YTLayer.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/8/26. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "UIView+YTLayer.h" 10 | 11 | @implementation UIView (YTLayer) 12 | 13 | - (void)cornerRadius:(CGFloat)radius { 14 | self.layer.masksToBounds = YES; 15 | self.layer.cornerRadius = radius; 16 | } 17 | 18 | - (void)borderWithColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth { 19 | self.layer.borderWidth = borderWidth; 20 | self.layer.borderColor = borderColor.CGColor; 21 | } 22 | 23 | - (void)cornerRadius:(CGFloat)radius borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth { 24 | self.layer.masksToBounds = YES; 25 | self.layer.cornerRadius = radius; 26 | self.layer.borderWidth = borderWidth; 27 | self.layer.borderColor = borderColor.CGColor; 28 | } 29 | 30 | - (void)makeRoundedCorner:(UIRectCorner)byRoundingCorners cornerRadii:(CGSize)cornerRadii { 31 | UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:byRoundingCorners cornerRadii:cornerRadii]; 32 | CAShapeLayer * shape = [CAShapeLayer layer]; 33 | shape.path = path.CGPath; 34 | self.layer.mask = shape; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/coreData/NSManagedObject+YTMoreThread.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+YTMoreThread.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/2. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | // 该类与YTCoreData.h结合使用,很好地解决啦多线程访问coredata问题。 9 | 10 | #import 11 | #import "YTCoreData.h" 12 | 13 | #define YTDBA [YTCoreData instance] 14 | 15 | typedef void(^Finished)(BOOL finished); 16 | typedef void(^Request)(NSFetchRequest *request); 17 | typedef void(^ListResult)(NSArray* result, NSError *error); 18 | typedef void(^ObjectResult)(id result, NSError *error); 19 | typedef id(^AsyncProcess)(NSManagedObjectContext *ctx, NSFetchRequest *request); 20 | 21 | @interface NSManagedObject (YTMoreThread) 22 | /**创建一个当前对象*/ 23 | + (id)createNew; 24 | 25 | /**保存同步*/ 26 | + (void)save:(OperationResult)handler; 27 | 28 | /* 29 | ** predicate -> NSPredicate 字符串标示 查询条件 30 | ** orders -> 存放多个NSString 如果需要降序:字符串前加"-" 31 | ** offset -> 数据偏移量 位置标示 32 | ** limit -> 取出多少条数据 33 | ** filter -> 数据(多个“NSManagedObject”类对象) 34 | */ 35 | + (NSArray *)filter:(NSString *)predicate orderby:(NSArray *)orders offset:(int)offset limit:(int)limit; 36 | 37 | /** 38 | ** 工能同上 -> 采用回调方法 handler 参数result = filter(up) 39 | */ 40 | + (void)filter:(NSString *)predicate orderby:(NSArray *)orders offset:(int)offset limit:(int)limit on:(ListResult)handler; 41 | 42 | /**根据查询条件取出一个"NSManagedObject"对象*/ 43 | + (id)one:(NSString *)predicate; 44 | 45 | /**根据查询条件取出一个"NSManagedObject"对象*/ 46 | + (void)one:(NSString *)predicate on:(ObjectResult)handler; 47 | 48 | /**根据查询条件取所有符合条件的"NSManagedObject"对象*/ 49 | + (void)removeAllObj:(NSString *)predicate finished:(Finished)finished; 50 | 51 | /**删除一个"NSManagedObject"类对象*/ 52 | + (void)delobject:(id)object; 53 | 54 | /**自定义条件取出多个“NSManagedObject”类对象 - 回调方法*/ 55 | + (void)async:(AsyncProcess)processBlock result:(ListResult)resultBlock; 56 | 57 | /**谓语查询数据库包含此对象数量*/ 58 | + (NSUInteger)count:(NSString *)predicate; 59 | 60 | /**ios8新方法*/ 61 | + (void)asyncRequest:(Request)requestBlock result:(ListResult)resultBlock; 62 | @end 63 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/coreData/NSManagedObject+YTMoreThread.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObject+YTMoreThread.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/2. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "NSManagedObject+YTMoreThread.h" 10 | 11 | typedef void (^ObjectForId)(NSArray * result); 12 | 13 | @implementation NSManagedObject (YTMoreThread) 14 | 15 | + (id)createNew{ 16 | NSString *className = [NSString stringWithUTF8String:object_getClassName(self)]; 17 | return [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:YTDBA.mainObjectContext]; 18 | } 19 | 20 | + (void)save:(OperationResult)handler{ 21 | [YTDBA save:handler]; 22 | } 23 | 24 | + (NSArray *)filter:(NSString *)predicate orderby:(NSArray *)orders offset:(int)offset limit:(int)limit{ 25 | NSManagedObjectContext *ctx = YTDBA.mainObjectContext; 26 | NSFetchRequest *fetchRequest = [self makeRequest:ctx predicate:predicate orderby:orders offset:offset limit:limit]; 27 | 28 | NSError* error = nil; 29 | NSArray* results = [ctx executeFetchRequest:fetchRequest error:&error]; 30 | if (error) { 31 | NSLog(@"error: %@", error); 32 | return @[]; 33 | } 34 | return results; 35 | } 36 | 37 | + (NSFetchRequest *)makeRequest:(NSManagedObjectContext *)ctx predicate:(NSString *)predicate orderby:(NSArray *)orders offset:(int)offset limit:(int)limit{ 38 | NSString *className = [NSString stringWithUTF8String:object_getClassName(self)]; 39 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init]; 40 | [fetchRequest setEntity:[NSEntityDescription entityForName:className inManagedObjectContext:ctx]]; 41 | 42 | if (predicate) { 43 | [fetchRequest setPredicate:[NSPredicate predicateWithFormat:predicate]]; 44 | } 45 | 46 | NSMutableArray *orderArray = [[NSMutableArray alloc] init]; 47 | if (orders&&orders.count>0) { 48 | for (NSString *order in orders) { 49 | NSSortDescriptor *orderDesc = nil; 50 | if ([[order substringToIndex:1] isEqualToString:@"-"]) { 51 | orderDesc = [[NSSortDescriptor alloc] initWithKey:[order substringFromIndex:1] 52 | ascending:NO]; 53 | }else{ 54 | orderDesc = [[NSSortDescriptor alloc] initWithKey:order 55 | ascending:YES]; 56 | } 57 | [orderArray addObject:orderDesc]; 58 | } 59 | [fetchRequest setSortDescriptors:orderArray]; 60 | } 61 | 62 | if (offset>0) { 63 | [fetchRequest setFetchOffset:offset]; 64 | } 65 | if (limit>0) { 66 | [fetchRequest setFetchLimit:limit]; 67 | } 68 | return fetchRequest; 69 | } 70 | 71 | + (void)filter:(NSString *)predicate orderby:(NSArray *)orders offset:(int)offset limit:(int)limit on:(ListResult)handler{ 72 | NSManagedObjectContext *ctx = [YTDBA createPrivateObjectContext]; 73 | 74 | [ctx performBlock:^{ 75 | NSFetchRequest *fetchRequest = [self makeRequest:ctx predicate:predicate orderby:orders offset:offset limit:limit]; 76 | NSError *error = nil; 77 | NSArray *results = [ctx executeFetchRequest:fetchRequest error:&error]; 78 | if (results&&results.count>0) { 79 | [[self class]objIds:results result:^(NSArray *result) { 80 | if (handler) handler(results, nil); 81 | }]; 82 | }else{ 83 | [YTDBA.mainObjectContext performBlock:^{ 84 | if (handler) handler(@[], error); 85 | }]; 86 | } 87 | }]; 88 | } 89 | 90 | 91 | + (id)one:(NSString *)predicate{ 92 | NSManagedObjectContext *ctx = YTDBA.mainObjectContext; 93 | NSFetchRequest *fetchRequest = [self makeRequest:ctx predicate:predicate orderby:nil offset:0 limit:1]; 94 | 95 | NSError *error = nil; 96 | NSArray *results = [ctx executeFetchRequest:fetchRequest error:&error]; 97 | if ([results count]!=1) { 98 | return nil; 99 | //raise(1); 100 | } 101 | return results[0]; 102 | } 103 | 104 | + (void)one:(NSString *)predicate on:(ObjectResult)handler{ 105 | NSManagedObjectContext *ctx = [YTDBA createPrivateObjectContext]; 106 | 107 | [ctx performBlock:^{ 108 | NSFetchRequest *fetchRequest = [self makeRequest:ctx predicate:predicate orderby:nil offset:0 limit:1]; 109 | NSError *error = nil; 110 | NSArray *results = [ctx executeFetchRequest:fetchRequest error:&error]; 111 | id obj = nil; 112 | if (results&&results.count>0) { 113 | NSManagedObjectID *objId = ((NSManagedObject*)results[0]).objectID; 114 | obj = [YTDBA.mainObjectContext objectWithID:objId]; 115 | } 116 | [YTDBA.mainObjectContext performBlock:^{ 117 | if (handler) handler(obj, error); 118 | }]; 119 | }]; 120 | } 121 | 122 | + (void)removeAllObj:(NSString *)predicate finished:(Finished)finished{ 123 | [self filter:predicate orderby:nil offset:0 limit:0 on:^(NSArray *result, NSError *error) { 124 | 125 | for (NSManagedObject *obj in result) { 126 | NSManagedObject *safeobject = [YTDBA.mainObjectContext objectWithID:obj.objectID]; 127 | [self delobject:safeobject]; 128 | } 129 | 130 | dispatch_async(dispatch_get_main_queue(), ^{ 131 | [YTDBA save:^(NSError *error) {}]; 132 | finished(YES); 133 | 134 | }); 135 | }]; 136 | } 137 | 138 | + (void)delobject:(id)object{ 139 | [YTDBA.mainObjectContext deleteObject:object]; 140 | } 141 | 142 | + (void)async:(AsyncProcess)processBlock result:(ListResult)resultBlock{ 143 | if (!processBlock) return ; 144 | NSString *className = [NSString stringWithUTF8String:object_getClassName(self)]; 145 | NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:className]; 146 | NSManagedObjectContext *ctx = [YTDBA createPrivateObjectContext]; 147 | 148 | [ctx performBlock:^{ 149 | id resultList = processBlock(ctx, request); 150 | if (resultList&&[resultList isKindOfClass:[NSArray class]]) { 151 | [[self class]objIds:resultList result:^(NSArray *result) { 152 | if (resultBlock) resultBlock(result, nil); 153 | }]; 154 | }else{ 155 | if (resultBlock) resultBlock(@[], nil); 156 | } 157 | }]; 158 | } 159 | 160 | + (void)objIds:(NSArray *)Ids result:(ObjectForId)objHander{ 161 | NSMutableArray *idArray = [NSMutableArray array]; 162 | for (NSManagedObject *obj in Ids) { 163 | [idArray addObject:obj.objectID]; 164 | } 165 | 166 | [YTDBA.mainObjectContext performBlock:^{ 167 | NSMutableArray *objArray = [NSMutableArray array]; 168 | for (NSManagedObjectID *robjId in idArray) { 169 | [objArray addObject:[YTDBA.mainObjectContext objectWithID:robjId]]; 170 | } 171 | if (objHander) objHander([objArray copy]); 172 | }]; 173 | } 174 | 175 | + (NSUInteger)count:(NSString *)predicate{ 176 | NSString *className = [NSString stringWithUTF8String:object_getClassName(self)]; 177 | NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:className]; 178 | [request setPredicate:[NSPredicate predicateWithFormat:predicate]]; 179 | return [YTDBA.mainObjectContext countForFetchRequest:request error:nil]; 180 | } 181 | 182 | + (void)asyncRequest:(Request)request result:(ListResult)resultBlock{ 183 | [YTDBA.createPrivateObjectContext performBlock:^{ 184 | NSString *className = [NSString stringWithUTF8String:object_getClassName(self)]; 185 | NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className]; 186 | if (request) request(fetchRequest); 187 | NSAsynchronousFetchRequest *requ = [[NSAsynchronousFetchRequest alloc]initWithFetchRequest:fetchRequest completionBlock:^(NSAsynchronousFetchResult *result) { 188 | [[self class] objIds:result.finalResult result:^(NSArray *result) { 189 | if (resultBlock) resultBlock(result,nil); 190 | }]; 191 | }]; 192 | [YTDBA.mainObjectContext executeRequest:requ error:nil]; 193 | }]; 194 | } 195 | 196 | @end 197 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/coreData/YTCoreData.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTCoreData.h 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/2. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef void(^OperationResult)(NSError* error); 13 | 14 | @interface YTCoreData : NSObject 15 | @property (readonly, strong, nonatomic) NSOperationQueue *queue; 16 | @property (readonly ,strong, nonatomic) NSManagedObjectContext *privateObjectContext; 17 | @property (readonly, strong, nonatomic) NSManagedObjectContext *mainObjectContext; 18 | 19 | /**单例模式*/ 20 | + (YTCoreData *)instance; 21 | 22 | /**创建一个队列context 做异步处理 -> 主要怎对线程和大量数据*/ 23 | - (NSManagedObjectContext *)createPrivateObjectContext; 24 | 25 | /**保存同步coreData*/ 26 | - (void)save:(OperationResult)handler; 27 | @end 28 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/coreData/YTCoreData.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTCoreData.m 3 | // YTChatDemo 4 | // 5 | // Created by TI on 15/9/2. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTCoreData.h" 10 | 11 | 12 | @interface YTCoreData() 13 | { 14 | NSString *_modelName; 15 | NSString *_fileName; 16 | NSManagedObjectModel *managedObjectModel; 17 | NSPersistentStoreCoordinator *persistentStoreCoordinator; 18 | } 19 | @end 20 | 21 | @implementation YTCoreData 22 | 23 | static YTCoreData * singleModel; 24 | + (YTCoreData *)instance{ 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | singleModel = [[YTCoreData alloc]init]; 28 | }); 29 | return singleModel; 30 | } 31 | 32 | - (instancetype)init{ 33 | if (self = [super init]) { 34 | NSDictionary *infodic =[[NSBundle mainBundle]infoDictionary]; 35 | _modelName = [infodic valueForKey:(NSString*)kCFBundleNameKey]; 36 | _fileName = [_modelName stringByAppendingString:@".sqlite"]; 37 | [self initCoreData]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)initCoreData{ 43 | _privateObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 44 | [_privateObjectContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]]; 45 | _mainObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 46 | [_mainObjectContext setParentContext:_privateObjectContext]; 47 | } 48 | 49 | - (NSManagedObjectContext *)createPrivateObjectContext 50 | { 51 | NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 52 | [ctx setParentContext:_mainObjectContext]; 53 | return ctx; 54 | } 55 | 56 | - (NSPersistentStoreCoordinator*)persistentStoreCoordinator{ 57 | if (persistentStoreCoordinator != nil) { 58 | return persistentStoreCoordinator; 59 | } 60 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:_fileName]; 61 | NSError *error = nil; 62 | persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 63 | if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 64 | NSLog(@"Unresolved error %@", error); 65 | //abort(); 66 | } 67 | return persistentStoreCoordinator; 68 | } 69 | 70 | - (NSURL *)applicationDocumentsDirectory 71 | { 72 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 73 | } 74 | 75 | - (NSManagedObjectModel *)managedObjectModel 76 | { 77 | if (managedObjectModel != nil) { 78 | return managedObjectModel; 79 | } 80 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:_modelName withExtension:@"momd"]; 81 | managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 82 | return managedObjectModel; 83 | } 84 | 85 | - (void)save:(OperationResult)handler{ 86 | NSError *error; 87 | if ([_mainObjectContext hasChanges]) { 88 | [_mainObjectContext save:&error]; 89 | [_privateObjectContext performBlock:^{ 90 | __block NSError *inner_error = nil; 91 | [_privateObjectContext save:&inner_error]; 92 | if (handler){ 93 | [_mainObjectContext performBlock:^{ 94 | handler(inner_error); 95 | }]; 96 | } 97 | }]; 98 | } 99 | if (error) { 100 | NSLog(@" save: error %@, %@",error,[error userInfo]); 101 | } 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/other/YTDeviceTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTDeviceTest.h 3 | // YTImageBrowser 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | // 这是一个访问手机部分硬件是否别允许的简单封装 9 | 10 | #import 11 | 12 | 13 | @interface YTDeviceTest : NSObject 14 | 15 | /**判断相册是否被允许访问 返回YES为允许访问*/ 16 | + (BOOL)userAuthorizationPhotoStatus; 17 | 18 | /**判断相机是否被允许访问 返回YES为允许访问*/ 19 | + (BOOL)userAuthorizationCameraStatus; 20 | 21 | /**判断麦克风是否被允许访问 返回YES为允许访问*/ 22 | + (BOOL)userAuthorizationAudioStatus; 23 | 24 | /***/ 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/other/YTDeviceTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTDeviceTest.m 3 | // YTImageBrowser 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTDeviceTest.h" 10 | @import AssetsLibrary; 11 | @import AVFoundation; 12 | @import UIKit; 13 | 14 | @implementation YTDeviceTest 15 | 16 | + (BOOL)userAuthorizationPhotoStatus{//相册 17 | ALAuthorizationStatus statu = [ALAssetsLibrary authorizationStatus]; 18 | switch (statu) { 19 | case ALAuthorizationStatusAuthorized: 20 | case ALAuthorizationStatusDenied: 21 | return YES; 22 | default: 23 | { 24 | NSString *title = @"无法访问相册"; 25 | NSString *message = @"请在iPhone的“设置-隐私-照片”中允许访问您的照片"; 26 | [[self class]showTitle:title message:message]; 27 | return NO; 28 | } 29 | } 30 | } 31 | 32 | + (BOOL)userAuthorizationCameraStatus{//相机 33 | UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; 34 | AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 35 | switch (status) { 36 | case AVAuthorizationStatusNotDetermined: 37 | case AVAuthorizationStatusAuthorized: 38 | if ([UIImagePickerController isSourceTypeAvailable:sourceType]){ 39 | return YES; 40 | }else{ 41 | return NO; 42 | } 43 | default: 44 | { 45 | NSString *title = @"无法访问相机"; 46 | NSString *message = @"请在iPhone的“设置-隐私-相机”中允许访问您的相机"; 47 | [[self class]showTitle:title message:message]; 48 | return NO; 49 | } 50 | } 51 | } 52 | 53 | + (BOOL)userAuthorizationAudioStatus{ 54 | AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; 55 | switch (status) { 56 | case AVAuthorizationStatusAuthorized: 57 | return YES; 58 | case AVAuthorizationStatusDenied: 59 | case AVAuthorizationStatusRestricted: 60 | default: 61 | { 62 | NSString *title = @"无法访问麦克风"; 63 | NSString *message = @"请在iPhone的“设置-隐私-麦克风”中允许访问您的麦克风"; 64 | [[self class]showTitle:title message:message]; 65 | } 66 | case AVAuthorizationStatusNotDetermined: 67 | return NO; 68 | } 69 | } 70 | 71 | + (void)showTitle:(NSString *)title message:(NSString *)message{ 72 | UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:title message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil]; 73 | [alertView show]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/other/YTNetworkLoad.h: -------------------------------------------------------------------------------- 1 | // 2 | // YTNetworkLoad.h 3 | // YTImageBrowser 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | // 轻量级网络数据请求,没法和那些第三方比。若不放心,建议替换。 9 | 10 | #import 11 | 12 | typedef void (^progressBlock)(float progress); 13 | 14 | @interface YTNetworkLoad : NSOperation 15 | 16 | @property (nonatomic, copy) progressBlock updataBlock; 17 | 18 | - (instancetype)initWithUrl:(NSURL*)url success:(void (^)(NSURLRequest *request, id data))success failure:(void (^)(NSError *error))failure; 19 | 20 | - (void)cancel; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /YTChatDemo/Tools/other/YTNetworkLoad.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTNetworkLoad.m 3 | // YTImageBrowser 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import "YTNetworkLoad.h" 10 | @import UIKit; 11 | 12 | typedef void (^block)(); 13 | 14 | @interface YTNetworkLoad(){ 15 | float totalLength; 16 | BOOL isSuccess; 17 | } 18 | 19 | @property (nonatomic, strong) NSURLConnection *connection; 20 | @property (nonatomic, strong) NSMutableData *dataSource; 21 | @property (nonatomic, strong) NSError *error; 22 | @property (nonatomic, copy) block result_block; 23 | @property (nonatomic, assign) float changeLength; 24 | @end 25 | 26 | @implementation YTNetworkLoad 27 | 28 | - (instancetype)initWithUrl:(NSURL *)url success:(void (^)(NSURLRequest *, id))success failure:(void (^)(NSError *))failure{ 29 | if (self = [super init]) { 30 | [self beginLoadUrl:url success:success failure:failure]; 31 | } 32 | return self; 33 | } 34 | 35 | -(void)dealloc{ 36 | [self cancel]; 37 | } 38 | 39 | - (void)beginLoadUrl:(NSURL *)url success:(void (^)(NSURLRequest *,id))success failure:(void (^)(NSError *))failure{ 40 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 41 | request.networkServiceType = NSURLNetworkServiceTypeBackground; 42 | self.connection = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO]; 43 | [self.connection setDelegateQueue:[NSOperationQueue currentQueue]]; 44 | 45 | __weak __typeof (self)weekself = self; 46 | _result_block = ^(){ 47 | if (isSuccess == YES) { 48 | success(weekself.connection.currentRequest,weekself.dataSource); 49 | }else{ 50 | failure(weekself.error); 51 | } 52 | }; 53 | [self.connection start]; 54 | } 55 | 56 | - (void)cancel{ 57 | [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; 58 | [self.connection setDelegateQueue:nil]; 59 | [self.connection cancel]; 60 | self.connection = nil; 61 | } 62 | 63 | #define mark - Connection Delegate 64 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ 65 | [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; 66 | totalLength = MAX([response expectedContentLength], 1); 67 | self.dataSource = [NSMutableData data]; 68 | } 69 | 70 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ 71 | _changeLength += [data length]; 72 | [self.dataSource appendData:data]; 73 | if ((_changeLength < totalLength)&&_updataBlock) { 74 | _updataBlock(_changeLength/totalLength); 75 | } 76 | } 77 | 78 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ 79 | isSuccess = YES; 80 | [self resultBlock]; 81 | } 82 | 83 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ 84 | _error = error; 85 | [self resultBlock]; 86 | } 87 | 88 | - (void)resultBlock{ 89 | if (_result_block) { 90 | _result_block(); 91 | } 92 | [self cancel]; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /YTChatDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.YccTime.app.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /YTChatDemoTests/YTChatDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // YTChatDemoTests.m 3 | // YTChatDemoTests 4 | // 5 | // Created by TI on 15/8/24. 6 | // Copyright (c) 2015年 YccTime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface YTChatDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation YTChatDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------