公司最近作一個項目,其中有一個模塊是富文本編輯模塊,以前沒作個相似的功能模塊,原本覺得這個功能很常見應該會有已經造好的輪子,或許我只要找到輪子,研究下輪子,而後修改打磨輪子,這件事就八九不離十了。不過,仍是 too young to simple 了,有些事,仍是得本身去面對的,或許這就叫作成長,感受最近一年,對於編程這件事,更多了一點熱愛,我感受我不配過只會複製粘貼代碼的人生,編程須要有挑戰。因此,遇到困難,保持一份正念,路其實就在腳下,若是沒有困難,那就製造困哪,迎難而上,人生沒有白走的路,每一步都算數,毒雞湯就到此爲止,下面是乾貨了。html
實現的功能包含了:前端
後期有進行了性能的優化,能夠看個人這篇文章: iOS使用Instrument-Time Profiler工具分析和優化性能問題java
以及客戶端代碼開源託管地址:MMRichTextEdit
還有java實現的文件服務器代碼開源託管地址:javawebserverdemoios
沒圖沒真相,下面是幾張實現的效果圖
git
基本上有如下幾種的實現方案:github
前面三種方案都有了開源的實現,不過都不知足須要,只有第二種方案會比較接近一點,不過WebView結合JS的操做確實是性能不夠好,內存佔用也比較高, WordPress-Editor 、RichTextDemo ,這兩種方法實現的編輯器會明顯的感受到不夠流暢,而且離須要還有挺大的距離,全部沒有選擇在這基礎上進行二次開發。第三種方案在網上有比較多的人推薦,不過我想他們大概也只是推薦而已,真正實現起來須要花費大把的時間,須要填的坑有不少,考慮到時間有限,以及項目的進度安排,這個坑我就沒有去踩了。
我最終選擇的是第四種方案,這種方案好的地方就是UITableView、UITextView都是十分熟悉的組件,使用組合的模式經過以上的分析,理論上是沒有問題的,而且,UITableView有複用Cell的優點,因此時間性能和空間性能應該是不差的。web
使用UITableView集合UITextView的這種方案有不少細節須要注意編程
須要解決問題,好的是有些是已經有人遇到而且解決的,其餘的即便其餘人沒有遇到過,做爲第一個吃螃蟹的人,咱們詳細的去分析下其實也不難bash
實現上面效果的基本原理是:
1.在 cell 中設置好 text view 的 autolayout,讓 cell 能夠根據內容自適應大小
2.text view 中輸入內容,根據內容更新 textView 的高度
3.調用 tableView 的 beginUpdates 和 endUpdates,從新計算 cell 的高度
4.將 text view 更新後的數據保存,以避免 table view 滾動超過一屏再滾回來 text view 中的數據又不刷新成原來的數據了。服務器
注意:上面文章中提到的思路是對的,不過在開發過程當中遇到一個問題:使用自動佈局計算高度的方式調用 tableView 的 beginUpdates 和 endUpdates,從新計算 cell 的高度會出現一個嚴重的BUG,textView中的文字會偏移致使不在正確的位置,因此實際的項目中禁用了tableView自動計算Cell高度的特性,採用手動計算Cell高度的方式,具體的能夠看個人項目代碼。
2.這個問題很簡單,使用屬性文字就好了,下面直接貼代碼了
NSAttributedString結合NSTextAttachment就好了
/** 顯示圖片的屬性文字 */
- (NSAttributedString*)attrStringWithContainerWidth:(NSInteger)containerWidth {
if (!_attrString) {
CGFloat showImageWidth = containerWidth - MMEditConfig.editAreaLeftPadding - MMEditConfig.editAreaRightPadding - MMEditConfig.imageDeltaWidth;
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
CGRect rect = CGRectZero;
rect.size.width = showImageWidth;
rect.size.height = showImageWidth * self.image.size.height / self.image.size.width;
textAttachment.bounds = rect;
textAttachment.image = self.image;
NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:textAttachment];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@""];
[attributedString insertAttributedString:attachmentString atIndex:0];
_attrString = attributedString;
// 設置Size
CGRect tmpImageFrame = rect;
tmpImageFrame.size.height += MMEditConfig.editAreaTopPadding + MMEditConfig.editAreaBottomPadding;
_imageFrame = tmpImageFrame;
}
return _attrString;
}
複製代碼
3.這個問題比較棘手,我本身也是先把可能的狀況列出來,而後一個一個分支去處理這些狀況,不難就是麻煩,下面的文本是我寫在 備忘錄
上的狀況分析,- [x] 這種標識這種狀況已經實現,- [ ] 這種標識暫時未實現,後面這部分會進行優化,主要的工做已經完成了,優化的工做量不會很大了。
UITableView實現的編輯器
return換行狀況分析:
- [x] text節點:不處理
- [x] Image節點-前面:上面是text,光標移動到上面一行,而且在最後添加一個換行,定位光標在最後將
- [x] Image節點-前面:上面是圖片或者空,在上面添加一個Text節點,光標移動到上面一行,
- [x] Image節點-後面:下面是圖片或者空,在下面添加一個Text節點,光標移動到下面一行,
- [x] Image節點-後面:下面是text,光標移動到下面一行,而且在最前面添加一個換行,定位光標在最前面
Delete狀況分析:
- [x] Text節點-當前的Text不爲空-前面-:上面是圖片,定位光標到上面圖片的最後
- [x] Text節點-當前的Text不爲空-前面-:上面是Text,合併當前Text和上面Text 這種狀況不存在,在圖片刪除的時候進行合併
- [x] Text節點-當前的Text不爲空-前面-:上面是空,不處理
- [x] Text節點-當前的Text爲空-前面-沒有其餘元素(第一個)-:不處理
- [x] Text節點-當前的Text爲空-前面-有其餘元素-:刪除這一行,定位光標到下面圖片的最後
- [x] Text節點-當前的Text不爲空-後面-:正常刪除
- [x] Text節點-當前的Text爲空-後面-:正常刪除,和第三種狀況:爲空的狀況處理同樣
- [x] Image節點-前面-上面爲Text(不爲空)/Image定位到上面元素的後面
- [x] Image節點-前面-上面爲Text(爲空):刪除上面Text節點
- [x] Image節點-前面-上面爲空:不處理
- [ ] Image節點-後面-上面爲空(第一個位置)-列表只有一個元素:添加一個Text節點,刪除當前Image節點,光標放在添加的Text節點上 ****TODO:上面元素不處於顯示區域不可定位****
- [x] Image節點-後面-上面爲空(第一個位置)-列表多於一個元素:刪除當前節點,光標放在後面元素以前
- [x] Image節點-後面-上面爲圖片:刪除Image節點,定位到上面元素的後面
- [x] Image節點-後面-上面爲Text-下面爲圖片或者空:刪除Image節點,定位到上面元素的後面
- [x] Image節點-後面-上面爲Text-下面爲Text:刪除Image節點,合併下面的Text到上面,刪除下面Text節點,定位到上面元素的後面
圖片節點添加文字的狀況分析:
- [ ] 前面輸入文字
- [ ] 後面輸入文字
插入圖片的狀況分析:
- [x] activeIndex是Image節點-後面:下面添加一個圖片節點
- [x] activeIndex是Image節點-前面:上面添加一個圖片節點
- [x] activeIndex是Text節點:拆分光標先後內容插入一個圖片節點和Text節點
- [x] 圖片插入以後更新 activeIndexPath
複製代碼
基本上分析就到此爲止了,talk is cheap, show me code,下面就是代碼實現了。
下面是文字輸入框的Cell的主要代碼,包含了
UITextViewDelegate
回調方法 textViewDidChange
中處理Cell的高度自動拉伸UITextView
重寫 deleteBackward
方法進行的回調,具體的能夠額查看 MMTextView
這個類的實現,很簡單的一個實現。@implementation MMRichTextCell
// ...
- (void)updateWithData:(id)data indexPath:(NSIndexPath*)indexPath {
if ([data isKindOfClass:[MMRichTextModel class]]) {
MMRichTextModel* textModel = (MMRichTextModel*)data;
_textModel = textModel;
// 從新設置TextView的約束
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.equalTo(self);
make.bottom.equalTo(self).priority(900);
make.height.equalTo(@(textModel.textFrame.size.height));
}];
// Content
_textView.text = textModel.textContent;
// Placeholder
if (indexPath.row == 0) {
self.textView.showPlaceHolder = YES;
} else {
self.textView.showPlaceHolder = NO;
}
}
}
- (void)beginEditing {
[_textView becomeFirstResponder];
if (![_textView.text isEqualToString:_textModel.textContent]) {
_textView.text = _textModel.textContent;
// 手動調用回調方法修改
[self textViewDidChange:_textView];
}
if ([self curIndexPath].row == 0) {
self.textView.showPlaceHolder = YES;
} else {
self.textView.showPlaceHolder = NO;
}
}
# pragma mark - ......::::::: UITextViewDelegate :::::::......
- (void)textViewDidChange:(UITextView *)textView {
CGRect frame = textView.frame;
CGSize constraintSize = CGSizeMake(frame.size.width, MAXFLOAT);
CGSize size = [textView sizeThatFits:constraintSize];
// 更新模型數據
_textModel.textFrame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, size.height);
_textModel.textContent = textView.text;
_textModel.selectedRange = textView.selectedRange;
_textModel.isEditing = YES;
if (ABS(_textView.frame.size.height - size.height) > 5) {
// 從新設置TextView的約束
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.equalTo(self);
make.bottom.equalTo(self).priority(900);
make.height.equalTo(@(_textModel.textFrame.size.height));
}];
UITableView* tableView = [self containerTableView];
[tableView beginUpdates];
[tableView endUpdates];
}
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
textView.inputAccessoryView = [self.delegate mm_inputAccessoryView];
if ([self.delegate respondsToSelector:@selector(mm_updateActiveIndexPath:)]) {
[self.delegate mm_updateActiveIndexPath:[self curIndexPath]];
}
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView {
textView.inputAccessoryView = nil;
return YES;
}
- (void)textViewDeleteBackward:(MMTextView *)textView {
// 處理刪除
NSRange selRange = textView.selectedRange;
if (selRange.location == 0) {
if ([self.delegate respondsToSelector:@selector(mm_preDeleteItemAtIndexPath:)]) {
[self.delegate mm_preDeleteItemAtIndexPath:[self curIndexPath]];
}
} else {
if ([self.delegate respondsToSelector:@selector(mm_PostDeleteItemAtIndexPath:)]) {
[self.delegate mm_PostDeleteItemAtIndexPath:[self curIndexPath]];
}
}
}
@end
複製代碼
下面顯示圖片Cell的實現,主要包含了
UITextViewDelegate
回調方法 shouldChangeTextInRange
中處理換行和刪除,這個地方的刪除和Text編輯的Cell不同,因此在這邊作了特殊的處理,具體看一看 shouldChangeTextInRange
這個方法的處理方式。@implementation MMRichImageCell
// 省略部否代碼...
- (void)updateWithData:(id)data {
if ([data isKindOfClass:[MMRichImageModel class]]) {
MMRichImageModel* imageModel = (MMRichImageModel*)data;
// 設置舊的數據delegate爲nil
_imageModel.uploadDelegate = nil;
_imageModel = imageModel;
// 設置新的數據delegate
_imageModel.uploadDelegate = self;
CGFloat width = [MMRichTextConfig sharedInstance].editAreaWidth;
NSAttributedString* imgAttrStr = [_imageModel attrStringWithContainerWidth:width];
_textView.attributedText = imgAttrStr;
// 從新設置TextView的約束
[self.textView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.equalTo(self);
make.bottom.equalTo(self).priority(900);
make.height.equalTo(@(imageModel.imageFrame.size.height));
}];
self.reloadButton.hidden = YES;
// 根據上傳的狀態設置圖片信息
if (_imageModel.isDone) {
self.progressView.hidden = NO;
self.progressView.progress = _imageModel.uploadProgress;
self.reloadButton.hidden = YES;
}
if (_imageModel.isFailed) {
self.progressView.hidden = NO;
self.progressView.progress = _imageModel.uploadProgress;
self.reloadButton.hidden = NO;
}
if (_imageModel.uploadProgress > 0) {
self.progressView.hidden = NO;
self.progressView.progress = _imageModel.uploadProgress;
self.reloadButton.hidden = YES;
}
}
}
#pragma mark - ......::::::: UITextViewDelegate :::::::......
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// 處理換行
if ([text isEqualToString:@"\n"]) {
if (range.location == 0 && range.length == 0) {
// 在前面添加換行
if ([self.delegate respondsToSelector:@selector(mm_preInsertTextLineAtIndexPath:textContent:)]) {
[self.delegate mm_preInsertTextLineAtIndexPath:[self curIndexPath]textContent:nil];
}
} else if (range.location == 1 && range.length == 0) {
// 在後面添加換行
if ([self.delegate respondsToSelector:@selector(mm_postInsertTextLineAtIndexPath:textContent:)]) {
[self.delegate mm_postInsertTextLineAtIndexPath:[self curIndexPath] textContent:nil];
}
} else if (range.location == 0 && range.length == 2) {
// 選中和換行
}
}
// 處理刪除
if ([text isEqualToString:@""]) {
NSRange selRange = textView.selectedRange;
if (selRange.location == 0 && selRange.length == 0) {
// 處理刪除
if ([self.delegate respondsToSelector:@selector(mm_preDeleteItemAtIndexPath:)]) {
[self.delegate mm_preDeleteItemAtIndexPath:[self curIndexPath]];
}
} else if (selRange.location == 1 && selRange.length == 0) {
// 處理刪除
if ([self.delegate respondsToSelector:@selector(mm_PostDeleteItemAtIndexPath:)]) {
[self.delegate mm_PostDeleteItemAtIndexPath:[self curIndexPath]];
}
} else if (selRange.location == 0 && selRange.length == 2) {
// 處理刪除
if ([self.delegate respondsToSelector:@selector(mm_preDeleteItemAtIndexPath:)]) {
[self.delegate mm_preDeleteItemAtIndexPath:[self curIndexPath]];
}
}
}
return NO;
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
textView.inputAccessoryView = [self.delegate mm_inputAccessoryView];
if ([self.delegate respondsToSelector:@selector(mm_updateActiveIndexPath:)]) {
[self.delegate mm_updateActiveIndexPath:[self curIndexPath]];
}
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView {
textView.inputAccessoryView = nil;
return YES;
}
#pragma mark - ......::::::: MMRichImageUploadDelegate :::::::......
// 上傳進度回調
- (void)uploadProgress:(float)progress {
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress:progress];
});
}
// 上傳失敗回調
- (void)uploadFail {
[self.progressView setProgress:0.01f];
self.reloadButton.hidden = NO;
}
// 上傳完成回調
- (void)uploadDone {
[self.progressView setProgress:1.0f];
}
@end
複製代碼
圖片上傳模塊中,上傳的元素和上傳回調抽象了對應的協議,圖片上傳模塊是一個單利的管理類,管理進行中的上傳元素和排隊中的上傳元素,
@protocol UploadItemCallBackProtocal <NSObject>
- (void)mm_uploadProgress:(float)progress;
- (void)mm_uploadFailed;
- (void)mm_uploadDone:(NSString*)remoteImageUrlString;
@end
@protocol UploadItemProtocal <NSObject>
- (NSData*)mm_uploadData;
- (NSURL*)mm_uploadFileURL;
@end
複製代碼
圖片上傳使用的是 NSURLSessionUploadTask
類處理
completionHandler
回調中處理結果NSURLSessionDelegate
的方法 URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
中處理上傳進度NSURLSessionDelegate
的方法 URLSession:task:didCompleteWithError:
中處理失敗上傳管理類的關鍵代碼以下:
@interface MMFileUploadUtil () <NSURLSessionDataDelegate, NSURLSessionDelegate, NSURLSessionTaskDelegate>
@property (strong,nonatomic) NSURLSession * session;
@property (nonatomic, strong) NSMutableArray* uploadingItems;
@property (nonatomic, strong) NSMutableDictionary* uploadingTaskIDToUploadItemMap;
@property (nonatomic, strong) NSMutableArray* todoItems;
@property (nonatomic, assign) NSInteger maxUploadTask;
@end
@implementation MMFileUploadUtil
- (void)addUploadItem:(id<UploadItemProtocal, UploadItemCallBackProtocal>)uploadItem {
[self.todoItems addObject:uploadItem];
[self startNextUploadTask];
}
- (void)startNextUploadTask {
if (self.uploadingItems.count < _maxUploadTask) {
// 添加下一個任務
if (self.todoItems.count > 0) {
id<UploadItemProtocal, UploadItemCallBackProtocal> uploadItem = self.todoItems.firstObject;
[self.uploadingItems addObject:uploadItem];
[self.todoItems removeObject:uploadItem];
[self uploadItem:uploadItem];
}
}
}
- (void)uploadItem:(id<UploadItemProtocal, UploadItemCallBackProtocal>)uploadItem {
NSMutableURLRequest * request = [self TSuploadTaskRequest];
NSData* uploadData = [uploadItem mm_uploadData];
NSData* totalData = [self TSuploadTaskRequestBody:uploadData];
__block NSURLSessionUploadTask * uploadtask = nil;
uploadtask = [self.session uploadTaskWithRequest:request fromData:totalData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString* result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"completionHandler %@", result);
NSString* imgUrlString = @"";
NSError *JSONSerializationError;
id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&JSONSerializationError];
if ([obj isKindOfClass:[NSDictionary class]]) {
imgUrlString = [obj objectForKey:@"url"];
}
// 成功回調
// FIXME: ZYT uploadtask ???
id<UploadItemProtocal, UploadItemCallBackProtocal> uploadItem = [self.uploadingTaskIDToUploadItemMap objectForKey:@(uploadtask.taskIdentifier)];
if (uploadItem) {
if ([uploadItem respondsToSelector:@selector(mm_uploadDone:)]) {
[uploadItem mm_uploadDone:imgUrlString];
}
[self.uploadingTaskIDToUploadItemMap removeObjectForKey:@(uploadtask.taskIdentifier)];
[self.uploadingItems removeObject:uploadItem];
}
[self startNextUploadTask];
}];
[uploadtask resume];
// 添加到映射中
[self.uploadingTaskIDToUploadItemMap setObject:uploadItem forKey:@(uploadtask.taskIdentifier)];
}
#pragma mark - ......::::::: NSURLSessionDelegate :::::::......
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSLog(@"didCompleteWithError = %@",error.description);
// 失敗回調
if (error) {
id<UploadItemProtocal, UploadItemCallBackProtocal> uploadItem = [self.uploadingTaskIDToUploadItemMap objectForKey:@(task.taskIdentifier)];
if (uploadItem) {
if ([uploadItem respondsToSelector:@selector(mm_uploadFailed)]) {
[uploadItem mm_uploadFailed];
}
[self.uploadingTaskIDToUploadItemMap removeObjectForKey:@(task.taskIdentifier)];
[self.uploadingItems removeObject:uploadItem];
}
}
[self startNextUploadTask];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
NSLog(@"bytesSent:%@-totalBytesSent:%@-totalBytesExpectedToSend:%@", @(bytesSent), @(totalBytesSent), @(totalBytesExpectedToSend));
// 進度回調
id<UploadItemProtocal, UploadItemCallBackProtocal> uploadItem = [self.uploadingTaskIDToUploadItemMap objectForKey:@(task.taskIdentifier)];
if ([uploadItem respondsToSelector:@selector(mm_uploadProgress:)]) {
[uploadItem mm_uploadProgress:(totalBytesSent * 1.0f/totalBytesExpectedToSend)];
}
}
@end
複製代碼
圖片上傳的回調會經過 UploadItemCallBackProtocal
協議的實現方法回調到圖片編輯的模型中,更新對應的數據。圖片編輯的數據模型是 MMRichImageModel
,該模型實現了 UploadItemProtocal
和 UploadItemCallBackProtocal
協議,實現 UploadItemCallBackProtocal
的方法更新數據模型的同時,會經過delegate通知到Cell更新進度和失敗成功的狀態。 關鍵的實現以下
@implementation MMRichImageModel
- (void)setUploadProgress:(float)uploadProgress {
_uploadProgress = uploadProgress;
if ([_uploadDelegate respondsToSelector:@selector(uploadProgress:)]) {
[_uploadDelegate uploadProgress:uploadProgress];
}
}
- (void)setIsDone:(BOOL)isDone {
_isDone = isDone;
if ([_uploadDelegate respondsToSelector:@selector(uploadDone)]) {
[_uploadDelegate uploadDone];
}
}
- (void)setIsFailed:(BOOL)isFailed {
_isFailed = isFailed;
if ([_uploadDelegate respondsToSelector:@selector(uploadFail)]) {
[_uploadDelegate uploadFail];
}
}
#pragma mark - ......::::::: UploadItemCallBackProtocal :::::::......
- (void)mm_uploadProgress:(float)progress {
self.uploadProgress = progress;
}
- (void)mm_uploadFailed {
self.isFailed = YES;
}
- (void)mm_uploadDone:(NSString *)remoteImageUrlString {
self.remoteImageUrlString = remoteImageUrlString;
self.isDone = YES;
}
#pragma mark - ......::::::: UploadItemProtocal :::::::......
- (NSData*)mm_uploadData {
return UIImageJPEGRepresentation(_image, 0.6);
}
- (NSURL*)mm_uploadFileURL {
return nil;
}
@end
複製代碼
最終是要把內容序列化而後上傳到服務端的,咱們的序列化方案是轉換爲HTML,內容處理模塊主要包含了如下幾點:
這部分收尾的工做比較的簡單,下面是實現代碼:
#define kRichContentEditCache @"RichContentEditCache"
@implementation MMRichContentUtil
+ (NSString*)htmlContentFromRichContents:(NSArray*)richContents {
NSMutableString *htmlContent = [NSMutableString string];
for (int i = 0; i< richContents.count; i++) {
NSObject* content = richContents[i];
if ([content isKindOfClass:[MMRichImageModel class]]) {
MMRichImageModel* imgContent = (MMRichImageModel*)content;
[htmlContent appendString:[NSString stringWithFormat:@"<img src=\"%@\" width=\"%@\" height=\"%@\" />", imgContent.remoteImageUrlString, @(imgContent.image.size.width), @(imgContent.image.size.height)]];
} else if ([content isKindOfClass:[MMRichTextModel class]]) {
MMRichTextModel* textContent = (MMRichTextModel*)content;
[htmlContent appendString:textContent.textContent];
}
// 添加換行
if (i != richContents.count - 1) {
[htmlContent appendString:@"<br />"];
}
}
return htmlContent;
}
+ (BOOL)validateRichContents:(NSArray*)richContents {
for (int i = 0; i< richContents.count; i++) {
NSObject* content = richContents[i];
if ([content isKindOfClass:[MMRichImageModel class]]) {
MMRichImageModel* imgContent = (MMRichImageModel*)content;
if (imgContent.isDone == NO) {
return NO;
}
}
}
return YES;
}
+ (UIImage*)scaleImage:(UIImage*)originalImage {
float scaledWidth = 1242;
return [originalImage scaletoSize:scaledWidth];
}
+ (NSString*)saveImageToLocal:(UIImage*)image {
NSString *path=[self createDirectory:kRichContentEditCache];
NSData* data = UIImageJPEGRepresentation(image, 1.0);
NSString *filePath = [path stringByAppendingPathComponent:[self.class genRandomFileName]];
[data writeToFile:filePath atomically:YES];
return filePath;
}
// 建立文件夾
+ (NSString *)createDirectory:(NSString *)path {
BOOL isDir = NO;
NSString *finalPath = [CACHE_PATH stringByAppendingPathComponent:path];
if (!([[NSFileManager defaultManager] fileExistsAtPath:finalPath
isDirectory:&isDir]
&& isDir))
{
[[NSFileManager defaultManager] createDirectoryAtPath:finalPath
withIntermediateDirectories :YES
attributes :nil
error :nil];
}
return finalPath;
}
+ (NSString*)genRandomFileName {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
uint32_t random = arc4random_uniform(10000);
return [NSString stringWithFormat:@"%@-%@.png", @(timeStamp), @(random)];
}
@end
複製代碼
這個功能從選型定型到實現大概花費了3天的時間,由於時間緣由,有不少地方優化的不到位,若是看官有建議意見但願給我留言,我會繼續完善,或者你有時間歡迎加入這個項目,能夠一塊兒作得更好,代碼開源看下面的連接。
客戶端代碼開源託管地址:MMRichTextEdit
java實現的文件服務器代碼開源託管地址:javawebserverdemo
iOS UITextView 輸入內容實時更新cell的高度
如何實現移動端的圖文混排編輯功能?
JavaWeb實現文件上傳下載功能實例解析
使用NSURLSessionUploadTask完成上傳文件