preface
The company recently did a project, in which a module is rich text editing module. I didn’t make a similar function module before, but I thought this function was very common and there should be already made wheels. Maybe I just need to find the wheels, study the wheels, and then modify and polish the wheels. Perhaps this is called growing up. In the past year, I have become more and more passionate about programming. I feel that I am not worthy of a life where I can only copy and paste code, and programming needs challenges. Therefore, when encountering difficulties, keep a mindful mind, the road is actually under your feet, if there are no difficulties, then create difficulties which, rise to the difficulties, life is not a road in vain, every step counts, so that’s the end of the toxic chicken soup, the following is the dry goods.
The results of
The functions implemented include:
- Editor Text editing
- Editor picture editing
- Editor text and text mix edit
- Editor picture upload, with progress and failure tips, you can re-upload operation
- The editor model is transformed into HTML formatted content
- Simple local data storage and recovery editing implementation (draft box feature)
- The server with the accompanying Java implementation
For performance optimization, you can see my article: iOS uses instrument-time Profiler to analyze and optimize performance problems
And the client code open-source hosting address: MMRichTextEdit and the Java implementation of the file server code open-source hosting address: JavaWebServerDemo
No picture, no truth, here are a few of the implementation
The research analysis
Basically, there are the following implementation schemes:
- UITextView combined with NSAttributeString to achieve text and text mixed editing, this scheme can be found on the Internet corresponding open source code, such as SimpleWord implementation is used in this way, but the disadvantage is that the picture can not interact, such as add progress bar on the picture, add upload failure hint, Image click event handling, etc., is not an option if there is no such need.
- Using WebView through JS and native interactive implementation, such as wordpress-Editor, RichTextDemo, the main problem is that the performance is not good, and you need to know the front-end knowledge to get started.
- Use CoreText or TextKit, which also has the open source code to implement the scheme, such as YYText, which is very famous, but it uses the image insert edit image position is fixed, text is around the picture, so this does not meet my requirements, if you want to use this scheme, there are a lot of modifications. And CoreText/TextKit usage has a certain threshold.
- Using a UITableView in combination with a fake implementation of a UITextView, the main idea is that each Cell is a UITextView for text input or a UITextView for displaying images, and the reason we chose UITextView for image display is because the image position requires an input cursor, So UITextView combined with NSAttributeString works just fine. Image and text mixing, that is, the image display Cell and text display Cell can be mixed, the main work is to deal with cursor position input and cursor position deletion.
Selection of finalize the design
The first three schemes have open source implementation, but none of them meet the needs. Only the second scheme is a little closer, but the operation of WebView combined with JS is indeed not good enough and occupies high memory, wordpress-Editor, RichTextDemo, The editor implemented by these two methods will obviously feel not smooth enough, and there is still quite a long way to go, so there is no choice to carry out secondary development on this basis. The third scheme has been recommended by many people on the Internet, but I think they just recommend it. It will take a lot of time to realize it and there are many pits to fill. Considering the limited time and the schedule of the project, I will not step on this pit. I finally choose the fourth scheme. The advantage of this scheme is that both UITableView and UITextView are very familiar components, and there is no problem in theory by using the combined mode through the above analysis. Moreover, UITableView has the advantage of reusing cells. So the time performance and space performance should not be bad.
Implementation detail analysis
There are a lot of details to pay attention to in this scheme using the UITableView collection UITextView
- UITextView is added to the Cell, and text input is wrapped on a line or more than one line of Cell is automatically scaled
- Add UITextView to Cell to display image processing
- Delete and add pictures at the cursor processing, line feed processing
Problems need to be solved. The good thing is that some of them have already been met and solved by others. As the first person to eat crabs, it is not difficult for us to analyze them in detail even if others have not encountered them
- This problem happens to be encountered by some people. Here we directly send a link to the iOS UITextView input content to update the height of the cell in real time
The basic principle to achieve the above effect is as follows: 1. Set the Autolayout of the Text view in the cell, so that the cell can adjust the size according to the content. 2. Call beginUpdates and endUpdates of the tableView to recalculate the height of the cell by 4. Save the updated data of the Text View, so that the table View does not scroll more than one screen and then roll back to the original data in the Text View.
Note: The ideas in the above article are on the right track, but there is a problem with development: Call beginUpdates and endUpdates of tableView with automatic layout calculation of height, recalculate cell height will cause a serious BUG, text in textView will be offset so that it is not in the correct position, Therefore, the automatic calculation of Cell height by tableView is disabled in the actual project, and the manual calculation of Cell height is adopted. For details, please refer to my project code.
2. This problem is very simple, just use the attribute text, and then paste the code NSAttributedString with the NS ExtAttachment
/** Displays the image property text */
- (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;
/ / set the Size
CGRect tmpImageFrame = rect;
tmpImageFrame.size.height += MMEditConfig.editAreaTopPadding + MMEditConfig.editAreaBottomPadding;
_imageFrame = tmpImageFrame;
}
return _attrString;
}
Copy the code
3. The problem is tricky, I myself also listed the possible first, and then a a branch to handle these situations, it is not hard to is the trouble, the following text is I write on the memo, – [x] this logo that is implemented, – [] this temporary unrealized, behind the branch is optimized, The main work is done, so the optimization won’t be much.
UITableViewImplemented editorreturn- [x] Image node: - [x] Image node: - [x] Image node: - [x] Image node: - [x] Image node: Add a Text node and move the cursor to the top line, - [x] Image node - after: Delete: - [x] text node - current text is not empty - before - : - [x] Text node - current Text not empty - front - : top Text, merge current Text and top Text - [x] Text node - current Text not empty - front - : top Text, merge current Text and top Text Empty above, not processed - [x] Text node - current Text is empty - preceding - No other elements (first) - : not processed - [x] Text node - Current Text is empty - preceding - Other elements - : Delete this line and position the cursor to the end of the following image - [x] Text node - current Text not empty - after - : Normal delete - [x] Text node - Current Text is empty - after - : Normal delete, and the third case: - [x] Image node - front - top Text(not empty)/Image position after the top element - [x] Image node - front - top Text(empty) : delete the top Text node - [x] Image node - front - top empty: No processing - [] Image node - back - top empty (first position) - list has only one element: Add a Text node, delete the current Image node, place the cursor over the added Text node ****TODO: the above element is not in the display area and cannot be located **** - [x] Image node - behind - empty above (first position) - list more than one element: Delete current node, cursor before next element - [x] Image node - behind - Image above: Delete Image node, position after above element - [x] Image node - behind - Text above - Image below or empty: Delete Image node, position after above element - [x] Image node, position after above element - [x] Image node, position after above element - [x] Image node, position after above element - [x] Image node, position after above element - [x] Image node, position after above element - [x] Image node, position after above element, delete Image node, merge below Text to above element, delete below Text node, position after above element - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' - [x] activeIndex = 'Image' Add a picture node above - [x] activeIndex is a Text node: split the cursor before and after inserting a picture node and Text node - [x] Update activeIndexPath after inserting a pictureCopy the code
Talk is cheap, show me code, here is the code implementation.
Code implementation
Edit module
Text input box Cell implementation
The following is the main code for the text input box Cell, containing
- Initially set the height of the text editing Cell, text content, and whether the text is displayed in the Placeholder
- in
UITextViewDelegate
The callback methodtextViewDidChange
Handles high automatic stretching of cells in - The delete callback method handles both previous and subsequent deletions, and the proxy method for deleting a callback is inheritance
UITextView
rewritedeleteBackward
Method, which you can view for detailsMMTextView
The implementation of this class, a very simple implementation.
@implementation MMRichTextCell
// ...
- (void)updateWithData:(id)data indexPath:(NSIndexPath*)indexPath {
if ([data isKindOfClass:[MMRichTextModel class]]) {
MMRichTextModel* textModel = (MMRichTextModel*)data;
_textModel = textModel;
// Resets TextView constraints
[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;// Manually invoke the callback method modification
[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];
// Update model data
_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) {
// Resets TextView constraints
[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 = [selfcontainerTableView]; [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 {
// Handle deletion
NSRange selRange = textView.selectedRange;
if (selRange.location == 0) {
if ([self.delegate respondsToSelector:@selector(mm_preDeleteItemAtIndexPath:)]) {
[self.delegate mm_preDeleteItemAtIndexPath:[selfcurIndexPath]]; }}else {
if ([self.delegate respondsToSelector:@selector(mm_PostDeleteItemAtIndexPath:)]) {
[self.delegate mm_PostDeleteItemAtIndexPath:[selfcurIndexPath]]; }}}@end
Copy the code
Display image Cell implementation
The following shows the implementation of the picture Cell, which mainly contains
- Initially set the height of the text editing Cell and the image display content
- in
UITextViewDelegate
The callback methodshouldChangeTextInRange
In, handle line feed and delete, delete this place and Text edit Cell is different, so do special processing here, take a look at the detailsshouldChangeTextInRange
The way this method is handled. - Handle picture upload progress callback, failure callback, success callback
@implementation MMRichImageCell - (void)updateWithData:(id)data {if([data isKindOfClass:[MMRichImageModel class]]) { MMRichImageModel* imageModel = (MMRichImageModel*)data; / / set the old data to the delegate to nil _imageModel. UploadDelegate = nil; _imageModel = imageModel; / / set the new data to the delegate _imageModel. UploadDelegate = self; CGFloat width = [MMRichTextConfig sharedInstance].editAreaWidth; NSAttributedString* imgAttrStr = [_imageModel attrStringWithContainerWidth:width]; _textView.attributedText = imgAttrStr; / / resetting TextView constraints [self TextView mas_remakeConstraints: ^ (make) MASConstraintMaker * {make. Left. Top. Right. EqualTo (self); make.bottom.equalTo(self).priority(900); make.height.equalTo(@(imageModel.imageFrame.size.height)); }]; self.reloadButton.hidden = YES; // Set the image information according to the uploaded statusif (_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 { // Processing lineif ([text isEqualToString:@"\n"]) {
if(range.location == 0 && range.length == 0) {// Add a newline to the frontif([self.delegate respondsToSelector:@selector(mm_preInsertTextLineAtIndexPath:textContent:)]) { [self.delegate mm_preInsertTextLineAtIndexPath:[self curIndexPath]textContent:nil]; }}else if(range.location == 1 && range.length == 0) {// Add a newline after itif([self.delegate respondsToSelector:@selector(mm_postInsertTextLineAtIndexPath:textContent:)]) { [self.delegate mm_postInsertTextLineAtIndexPath:[self curIndexPath] textContent:nil]; }}else if(range.location == 0 && range.length == 2) {// Select and newline}} // Handle deletionif ([text isEqualToString:@""]) {
NSRange selRange = textView.selectedRange;
if(selrange.location == 0 && selrange.length == 0) {// Process the deleteif([self.delegate respondsToSelector:@selector(mm_preDeleteItemAtIndexPath:)]) { [self.delegate mm_preDeleteItemAtIndexPath:[self curIndexPath]]; }}else if(selrange.location == 1 && selrange.length == 0) {// Process the deleteif([self.delegate respondsToSelector:@selector(mm_PostDeleteItemAtIndexPath:)]) { [self.delegate mm_PostDeleteItemAtIndexPath:[self curIndexPath]]; }}else if(selrange.location == 0 && selrange.length == 2) {// Process deletionif([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 :::::::......// uploadProgress callback - (void)uploadProgress:float)progress {
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress:progress]; }); } // Upload failure callback - (void)uploadFail {[self.progressViewsetProgress: 0.01 f]; self.reloadButton.hidden = NO; } // Upload completion callback - (void)uploadDone {[self.progressViewsetProgress: 1.0 f]; } @endCopy the code
Picture uploading module
In the picture upload module, the upload elements and the upload call abstract the corresponding protocol. The picture upload module is a simple management class, which manages the upload elements in progress and the upload elements in queue.
Image upload elements and an abstract protocol for callbacks
@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
Copy the code
Picture upload management class
Image uploadTask is handled by NSURLSessionUploadTask
- in
completionHandler
Callback to process the result - in
NSURLSessionDelegate
The method ofURLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
To process the upload progress - in
NSURLSessionDelegate
The method ofURLSession:task:didCompleteWithError:
Failed to process
The key code of upload management class is as follows:
@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) {
// Add the next task
if (self.todoItems.count > 0) {
id<UploadItemProtocal, UploadItemCallBackProtocal> uploadItem = self.todoItems.firstObject;
[self.uploadingItems addObject:uploadItem];
[self.todoItems removeObject:uploadItem];
[selfuploadItem: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"];
}
// Successful callback
// 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];
// Add to the map
[self.uploadingTaskIDToUploadItemMap setObject:uploadItem forKey:@(uploadtask.taskIdentifier)];
}
#pragma mark - ...... ::::::: NSURLSessionDelegate :::::::......- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSLog(@"didCompleteWithError = %@",error.description);
// Failed callback
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]; }} [selfstartNextUploadTask]; } - (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));
// Schedule callback
id<UploadItemProtocal, UploadItemCallBackProtocal> uploadItem = [self.uploadingTaskIDToUploadItemMap objectForKey:@(task.taskIdentifier)];
if ([uploadItem respondsToSelector:@selector(mm_uploadProgress:)]) {
[uploadItem mm_uploadProgress:(totalBytesSent * 1.0f/totalBytesExpectedToSend)]; }}@end
Copy the code
Image upload callback will pass UploadItemCallBackProtocal protocol implementation method of correction to the image editing model, update the corresponding data. Photo editing is MMRichImageModel data model, the model implements the UploadItemProtocal and UploadItemCallBackProtocal agreement, Implement UploadItemCallBackProtocal method to update the data model at the same time, will be informed by the delegate to the Cell update progress state of success and failure. The key implementation is as follows
@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
Copy the code
Content processing module
Finally, the content is serialized and uploaded to the server. Our serialization scheme is converted to HTML. The content processing module mainly contains the following points:
- Generate content in HTML format
- Verify whether the content is valid and judge that all pictures have been uploaded successfully
- The compressed image
- Save the image locally
This part of the final work is relatively simple, the following is the implementation code:
#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];
}
// Add a newline
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;
}
// Create a folder
+ (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
Copy the code
conclusion
It took about 3 days for this function to be completed from selection and finalization to implementation. Due to the time, there are many places where the optimization is not in place. If the reviewers have suggestions and suggestions, please leave a message to me, I will continue to improve.
Code hosting location
Client code open-source hosting address: MMRichTextEdit Java implementation of file server code open-source hosting address: JavaWebServerDemo
Refer to the link
IOS UITextView Input content Update cell height in real time How to achieve text and text mixing editing function on mobile terminals? NSURLSessionUploadTask Is used to upload files