Showing preview only (226K chars total). Download the full file or copy to clipboard to get everything.
Repository: littleMeaning/LMNote
Branch: master
Commit: db33b9b1caf6
Files: 103
Total size: 197.5 KB
Directory structure:
gitextract_xie_f35z/
├── .gitignore
├── LICENSE
├── LMNote/
│ ├── LMNote/
│ │ ├── .gitignore
│ │ ├── Feature
│ │ ├── LMNote.h
│ │ ├── LMNoteViewController.h
│ │ ├── LMNoteViewController.m
│ │ ├── Lines/
│ │ │ ├── LMNBulletsLine.h
│ │ │ ├── LMNBulletsLine.m
│ │ │ ├── LMNCheckboxLine.h
│ │ │ ├── LMNCheckboxLine.m
│ │ │ ├── LMNImageLine.h
│ │ │ ├── LMNImageLine.m
│ │ │ ├── LMNLine.h
│ │ │ ├── LMNLine.m
│ │ │ ├── LMNLineChain+Numbering.h
│ │ │ ├── LMNLineChain+Numbering.m
│ │ │ ├── LMNLineChain.h
│ │ │ ├── LMNLineChain.m
│ │ │ ├── LMNLineModes.h
│ │ │ ├── LMNNumberingLine.h
│ │ │ ├── LMNNumberingLine.m
│ │ │ ├── LMNSpecialLine.h
│ │ │ ├── LMNSpecialLine.m
│ │ │ ├── LMNTextLine.h
│ │ │ └── LMNTextLine.m
│ │ ├── OtherViews/
│ │ │ ├── LMNImageInputViewController.h
│ │ │ ├── LMNImageInputViewController.m
│ │ │ ├── LMNImageView.h
│ │ │ ├── LMNImageView.m
│ │ │ ├── LMNPhotoCollectionCell.h
│ │ │ ├── LMNPhotoCollectionCell.m
│ │ │ ├── LMNToolBar.h
│ │ │ ├── LMNToolBar.m
│ │ │ ├── LMNWebViewController.h
│ │ │ └── LMNWebViewController.m
│ │ ├── Store/
│ │ │ ├── LMNDraft.h
│ │ │ ├── LMNDraft.m
│ │ │ ├── LMNFolder.h
│ │ │ ├── LMNFolder.m
│ │ │ ├── LMNItem.h
│ │ │ ├── LMNItem.m
│ │ │ ├── LMNStore.h
│ │ │ ├── LMNStore.m
│ │ │ ├── NSTextAttachment+LMNStore.h
│ │ │ ├── NSTextAttachment+LMNStore.m
│ │ │ ├── UIImage+LMNStore.h
│ │ │ └── UIImage+LMNStore.m
│ │ └── TextView/
│ │ ├── LMNTextStorage+Export.h
│ │ ├── LMNTextStorage+Export.m
│ │ ├── LMNTextStorage.h
│ │ ├── LMNTextStorage.m
│ │ ├── LMNTextView.h
│ │ ├── LMNTextView.m
│ │ ├── UIFont+LMNote.h
│ │ └── UIFont+LMNote.m
│ ├── LMNote.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── LMNote.xcscheme
│ └── Supporting Files/
│ └── Info.plist
├── LMNote.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ └── IDEWorkspaceChecks.plist
├── LMNoteDemo/
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── FolderViewController.h
│ ├── FolderViewController.m
│ └── Util/
│ ├── LMColorPickerView.h
│ ├── LMColorPickerView.m
│ ├── LMTextHTMLParser.h
│ └── LMTextHTMLParser.m
├── LMNoteDemo.xcodeproj/
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ └── contents.xcworkspacedata
├── README.md
└── Supporting Files/
├── Assets.xcassets/
│ ├── AppIcon.appiconset/
│ │ └── Contents.json
│ ├── Contents.json
│ ├── baritem_folder.imageset/
│ │ └── Contents.json
│ ├── font/
│ │ ├── Contents.json
│ │ ├── lmn_font_bold.imageset/
│ │ │ └── Contents.json
│ │ ├── lmn_font_italic.imageset/
│ │ │ └── Contents.json
│ │ ├── lmn_font_strikethrough.imageset/
│ │ │ └── Contents.json
│ │ └── lmn_font_underline.imageset/
│ │ └── Contents.json
│ ├── lmn_accessory_checkbox.imageset/
│ │ └── Contents.json
│ ├── lmn_accessory_checkbox_.imageset/
│ │ └── Contents.json
│ ├── lmn_alignment_center.imageset/
│ │ └── Contents.json
│ ├── lmn_alignment_left.imageset/
│ │ └── Contents.json
│ ├── lmn_alignment_right.imageset/
│ │ └── Contents.json
│ ├── lmn_btn_edit.imageset/
│ │ └── Contents.json
│ ├── lmn_btn_plus.imageset/
│ │ └── Contents.json
│ ├── lmn_delete.imageset/
│ │ └── Contents.json
│ ├── lmn_left_square.imageset/
│ │ └── Contents.json
│ ├── lmn_list.imageset/
│ │ └── Contents.json
│ ├── lmn_list_checkbox.imageset/
│ │ └── Contents.json
│ ├── lmn_list_dot.imageset/
│ │ └── Contents.json
│ ├── lmn_list_number.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_a.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_close.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_image.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_subtitle.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_t.imageset/
│ │ └── Contents.json
│ └── lmn_tool_title.imageset/
│ └── Contents.json
├── Base.lproj/
│ └── LaunchScreen.storyboard
├── Info.plist
└── main.m
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 陈庆明
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: LMNote/LMNote/.gitignore
================================================
TODO
================================================
FILE: LMNote/LMNote/Feature
================================================
======================================== 待办 ========================================
功能: 颜色 | 视频 | 链接 | 引言
功能: toolBar 适配(通过 … 实现)
bug: emoji 高度计算错误
bug: 拼音输入过程中高度计算错误
bug: 找到修改 kLineSpacing 会导致整个计算错误的原因。
优化: 草稿,图片缓存优化,没有使用的图片定时清理
优化: 支持斜体。
优化: 图片非编辑状态应该收起
优化: 图片点击之后更丰富的处理
优化: 在选图片 collectionView 上下拉,选中状态会闪烁
优化: 图片前后输入文字处理。
======================================== 完成 ========================================
功能: 草稿
功能: 导出成 HTML
bug: checkbox 后插入图片计算错误
bug: 首行切换 checkbox 后换行显示错误
bug: 中间插入图片后位置不正确
bug: 首行图片后位置计算不正确
bug: 删除图片后位置计算不正确
bug: 连续插入图片位置不正确
bug: 粗体换行好计算错误
bug: 中文高度计算错误
bug: 图片插在 numbering 中间
bug: 行高计算小数精确位数影响结果
bug: 粗体和 underline 时候高度错误。
优化: bullets 换成图片
优化: 居中换行到下一行时,光标位置不正确。
优化: 插入图片时候主线程会卡顿一下,应该图片太大导致。
优化: 粗体换行后继承。
优化: numbering 之间间距太大。
优化: 插入位置为行首时,图片插入在前面
重构: Line 的扩展
================================================
FILE: LMNote/LMNote/LMNote.h
================================================
//
// LMNote.h
// LMNote
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
FOUNDATION_EXPORT double LMNoteVersionNumber;
FOUNDATION_EXPORT const unsigned char LMNoteVersionString[];
#import <LMNote/LMNTextView.h>
#import <LMNote/LMNoteViewController.h>
#import <LMNote/LMNStore.h>
#import <LMNote/LMNFolder.h>
#import <LMNote/LMNItem.h>
#import <LMNote/LMNDraft.h>
================================================
FILE: LMNote/LMNote/LMNoteViewController.h
================================================
//
// NoteViewController.h
// LMNoteDemo
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@class LMNDraft;
@interface LMNoteViewController : UIViewController
@property (nonatomic, readonly) LMNDraft *draft;
- (instancetype)initWithDraft:(LMNDraft *)draft;
@end
================================================
FILE: LMNote/LMNote/LMNoteViewController.m
================================================
//
// NoteViewController.m
// LMNoteDemo
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNoteViewController.h"
#import "LMNTextView.h"
#import "LMNToolBar.h"
#import "UIFont+LMNote.h"
#import "LMNStore.h"
#import "LMNDraft.h"
#import "LMNImageView.h"
#import "LMNImageInputViewController.h"
#import "LMNWebViewController.h"
@interface LMNoteViewController () <UITextViewDelegate, LMNToolBarDelegate, LMNImageInputViewControllerDelegate>
@property (nonatomic, strong) LMNTextView *textView;
@property (nonatomic, strong) LMNToolBar *toolBar;
@property (nonatomic, strong) UIButton *editButton;
@property (nonatomic, strong) LMNImageInputViewController *imageInputViewController;
@property (nonatomic, weak) UIViewController *currentInputController;
@property (nonatomic, assign) NSInteger cursorIndex;
@property (nonatomic, assign) CGFloat keyboardHeight;
@end
@implementation LMNoteViewController
- (instancetype)initWithDraft:(LMNDraft *)draft
{
self = [super init];
if (self) {
_draft = draft;
}
return self;
}
- (void)loadSubviews
{
self.textView = ({
LMNTextView *textView = [[LMNTextView alloc] initWithTextStorage:self.draft.textStorage];
textView.backgroundColor = [UIColor whiteColor];
textView.spellCheckingType = UITextSpellCheckingTypeNo;
textView.delegate = self;
textView;
});
[self.view addSubview:self.textView];
self.editButton = ({
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:[UIImage imageNamed:@"lmn_btn_edit"] forState:UIControlStateNormal];
button.backgroundColor = [UIColor colorWithWhite:.5f alpha:.75f];
button.clipsToBounds = YES;
[button addTarget:self action:@selector(showToolBar:) forControlEvents:UIControlEventTouchUpInside];
button;
});
[self.view addSubview:self.editButton];
self.toolBar = ({
LMNToolBar *toolBar = [LMNToolBar toolBar];
toolBar.delegate = self;
toolBar.hidden = YES;
toolBar;
});
[self.view addSubview:self.toolBar];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self loadSubviews];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"HTML" style:UIBarButtonItemStylePlain target:self action:@selector(export)];
}
- (void)layoutTextView
{
CGFloat toolbarHeight = 44.f;
self.textView.frame = self.view.bounds;
self.toolBar.frame = ({
CGRect rect = self.view.bounds;
rect.size.height = toolbarHeight;
rect.origin.y = CGRectGetHeight(self.view.bounds) - CGRectGetHeight(rect) - self.keyboardHeight;
rect;
});
UIEdgeInsets insets = self.textView.contentInset;
insets.bottom = self.keyboardHeight + 10.f;
if (self.toolBar.hidden == NO) {
insets.bottom += toolbarHeight;
}
self.textView.contentInset = insets;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self layoutTextView];
self.editButton.frame = ({
CGRect rect = CGRectZero;
rect.size = CGSizeMake(50.f, 50.f);
rect.origin.x = CGRectGetWidth(self.view.bounds) - 50.f - 20.f;
rect.origin.y = CGRectGetHeight(self.view.bounds) - 50.f - 20.f - self.keyboardHeight;
rect;
});
self.editButton.layer.cornerRadius = 25.f;
self.toolBar.frame = ({
CGRect rect = self.view.bounds;
rect.origin.y = CGRectGetHeight(rect) - 44.f - self.keyboardHeight;
rect.size.height = 44.f;
rect;
});
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self addObservers];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self removeObservers];
[self saveDraft];
}
- (void)addObservers
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)removeObservers
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)saveDraft
{
__block NSString *firstLine = nil;
NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet];
[self.textView.text enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) {
line = [line stringByTrimmingCharactersInSet:characterSet];
if (line.length > 0) {
firstLine = line;
*stop = YES;
}
}];
if (firstLine.length > 0) {
self.draft.name = firstLine;
self.draft.textStorage = (LMNTextStorage *)self.textView.textStorage;
[self.draft save];
}
else {
[self.draft delete];
}
}
- (void)export
{
[self.textView exportHTML:^(BOOL succeed, NSString *html) {
NSLog(@"html:\n%@", html);
LMNWebViewController *vc = [[LMNWebViewController alloc] init];
vc.html = html;
[self.navigationController pushViewController:vc animated:YES];
}];
}
#pragma mark - input
- (LMNImageInputViewController *)imageInputViewController
{
if (!_imageInputViewController) {
_imageInputViewController = [[LMNImageInputViewController alloc] init];
_imageInputViewController.delegate = self;
}
return _imageInputViewController;
}
- (void)changeTextInputForTag:(LMNToolBarItemTag)tag
{
UIViewController *inputViewController = nil;
switch (tag) {
case LMNToolBarItemTagImage:
inputViewController = self.imageInputViewController;
self.cursorIndex = NSMaxRange(self.textView.selectedRange);
break;
default:
break;
}
if (inputViewController && self.currentInputController != inputViewController) {
CGRect rect = self.view.bounds;
rect.size.height = 260.f;
rect.origin.y = CGRectGetHeight(self.view.bounds) - CGRectGetHeight(rect);
inputViewController.view.frame = rect;
}
self.currentInputController = inputViewController;
[self.textView resignFirstResponder];
}
#pragma mark - private
- (void)showToolBar:(UIButton *)button
{
self.editButton.hidden = YES;
self.toolBar.hidden = NO;
[self.textView becomeFirstResponder];
}
- (void)reloadToolBar
{
NSMutableDictionary *attributes = [self.textView.typingAttributes mutableCopy];
LMNLineMode mode = [self.textView lineModeForRange:self.textView.selectedRange];
NSString *selectedStr = [self.textView.text substringWithRange:self.textView.selectedRange];
BOOL isMultiLine = [selectedStr containsString:@"\n"];
[self.toolBar reloadDataWithTypingAttributes:attributes mode:mode isMultiLine:isMultiLine];
}
- (void)showInputView
{
CGRect rect = self.currentInputController.view.frame;
[self.view addSubview:self.currentInputController.view];
self.currentInputController.view.frame = CGRectOffset(rect, 0, CGRectGetHeight(rect));
[UIView animateWithDuration:0.3 animations:^{
self.currentInputController.view.frame = rect;
}];
}
- (void)hideInputView
{
if (self.currentInputController) {
[UIView animateWithDuration:0.3 animations:^{
CGRect rect = self.currentInputController.view.frame;
self.currentInputController.view.frame = CGRectOffset(rect, 0, CGRectGetHeight(rect));
} completion:^(BOOL finished) {
[self.currentInputController.view removeFromSuperview];
self.currentInputController = nil;
}];
}
[self.view setNeedsLayout];
}
#pragma mark - Keyboard
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
if (self.keyboardHeight == keyboardSize.height) {
return;
}
self.keyboardHeight = keyboardSize.height;
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self layoutTextView];
} completion:nil];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
if (self.keyboardHeight == 0) {
return;
}
self.keyboardHeight = 0;
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self layoutTextView];
} completion:nil];
}
#pragma mark - <UITextViewDelegate>
- (void)textViewDidBeginEditing:(UITextView *)textView
{
self.editButton.hidden = YES;
self.toolBar.hidden = NO;
[self hideInputView];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
self.toolBar.hidden = YES;
if (self.currentInputController) {
[self showInputView];
}
else {
self.editButton.hidden = NO;
}
[self.view setNeedsLayout];
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[self reloadToolBar];
}
#pragma mark - <LMNToolBarDelegate>
- (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedMode:(LMNLineMode)mode
{
[self.textView setLineMode:mode forRange:self.textView.selectedRange];
[self reloadToolBar];
}
- (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedAttributes:(NSDictionary *)attributes
{
BOOL bold = [attributes[LMFontBoldAttributeName] boolValue];
BOOL italic = [attributes[LMFontItalicAttributeName] boolValue];
BOOL underline = [attributes[LMFontUnderlineAttributeName] boolValue];
BOOL strikethrough = [attributes[LMFontStrikethroughAttributeName] boolValue];
NSMutableDictionary *typingAttributes = [self.textView.typingAttributes mutableCopy];
UIFont *font = typingAttributes[NSFontAttributeName];
typingAttributes[NSFontAttributeName] = [UIFont fontWithFontSize:font.fontSize bold:bold italic:italic];
if (underline) {
typingAttributes[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
}
else {
[typingAttributes removeObjectForKey:NSUnderlineStyleAttributeName];
}
if (strikethrough) {
typingAttributes[NSStrikethroughStyleAttributeName] = @(1);
}
else {
[typingAttributes removeObjectForKey:NSStrikethroughStyleAttributeName];
}
self.textView.typingAttributes = typingAttributes;
[self.textView setAttributesForSelection:typingAttributes];
}
- (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedTextAlignment:(NSTextAlignment)alignment
{
[self.textView setTextAlignmentForSelection:alignment];
}
- (void)lmn_toolBar:(LMNToolBar *)toolBar didSelectedItemWithTag:(LMNToolBarItemTag)tag
{
[self changeTextInputForTag:tag];
}
- (void)lmn_toolBarClose:(LMNToolBar *)toolBar
{
self.editButton.hidden = NO;
self.toolBar.hidden = YES;
[self.textView resignFirstResponder];
}
#pragma mark - <LMNImageInputViewControllerDelegate>
- (void)lmn_imageInput:(LMNImageInputViewController *)viewController didSelectPHAsset:(PHAsset *)asset
{
[self hideInputView];
self.editButton.hidden = NO;
CGFloat imageWidth = CGRectGetWidth([UIScreen mainScreen].bounds) - 40.f;
CGSize targetSize = CGSizeMake(imageWidth, imageWidth);
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.networkAccessAllowed = YES;
__block LMNImageView *imageView = nil;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *result, NSDictionary *info) {
if (!imageView) {
imageView = [self.textView insertImage:result atIndex:self.cursorIndex];
}
else {
imageView.image = result;
}
}];
}
- (void)lmn_imageInputClose:(LMNImageInputViewController *)viewController
{
[self hideInputView];
self.editButton.hidden = NO;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNBulletsLine.h
================================================
//
// LMNBulletsLine.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNSpecialLine.h"
@interface LMNBulletsLine : LMNSpecialLine
@end
================================================
FILE: LMNote/LMNote/Lines/LMNBulletsLine.m
================================================
//
// LMNBulletsLine.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNBulletsLine.h"
@interface LMNBulletsLine ()
@property (nonatomic, strong) UIImageView *bulletImageView;
@end
@implementation LMNBulletsLine
- (UIImage *)bulletImage
{
static dispatch_once_t onceToken;
static UIImage *image;
dispatch_once(&onceToken, ^{
CGFloat diameter = 6.f;
CGFloat scale = [UIScreen mainScreen].scale;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), NO, scale);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, diameter, diameter)];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddPath(ctx, path.CGPath);
CGContextSetFillColorWithColor(ctx, [UIColor darkGrayColor].CGColor);
CGContextDrawPath(ctx, kCGPathFill);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
return image;
}
#pragma - left view
- (void)loadLeftView
{
if (self.bulletImageView) {
return;
}
self.bulletImageView = ({
UIImageView *imageView = [[UIImageView alloc] initWithImage:self.bulletImage];
imageView.contentMode = UIViewContentModeCenter;
imageView;
});
}
- (UIView *)leftView
{
return self.bulletImageView;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNCheckboxLine.h
================================================
//
// LMNCheckboxLine.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNSpecialLine.h"
@interface LMNCheckboxLine : LMNSpecialLine
@property (nonatomic, assign) BOOL checkboxSelected;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNCheckboxLine.m
================================================
//
// LMNCheckboxLine.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNCheckboxLine.h"
@interface LMNCheckboxLine ()
@property (nonatomic, strong) UIButton *checkbox;
@end
@implementation LMNCheckboxLine
static CGFloat const kCheckboxLineHeight = 30.f;
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.checkboxSelected = [aDecoder decodeBoolForKey:@"checkboxSelected"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeBool:self.checkboxSelected forKey:@"checkboxSelected"];
}
- (NSDictionary *)attributes
{
static NSDictionary *attributes;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
attributes = [self attributesWithFont:nil];
UIFont *font = attributes[NSFontAttributeName];
CGFloat paragraphSpacing = (kCheckboxLineHeight - roundf(font.lineHeight)) / 2.f;
NSMutableParagraphStyle *paragraphStyle = attributes[NSParagraphStyleAttributeName];
paragraphStyle.paragraphSpacing = paragraphSpacing;
paragraphStyle.paragraphSpacingBefore = paragraphSpacing;
attributes = [attributes copy];
});
return attributes;
}
#pragma mark - checkbox
- (void)setCheckboxSelected:(BOOL)checkboxSelected
{
_checkboxSelected = checkboxSelected;
self.checkbox.selected = checkboxSelected;
}
- (void)selectCheckbox:(id)sender
{
self.checkboxSelected = !self.checkboxSelected;
}
#pragma - left view
- (CGSize)intrinsicLeftSize
{
return CGSizeMake(kCheckboxLineHeight, kCheckboxLineHeight);
}
- (void)loadLeftView
{
if (self.checkbox) {
return;
}
self.checkbox = ({
UIImage *imageNormal = [UIImage imageNamed:@"lmn_accessory_checkbox"];
UIImage *imageSelected = [UIImage imageNamed:@"lmn_accessory_checkbox_"];
UIButton *checkbox = [UIButton buttonWithType:UIButtonTypeCustom];
[checkbox setImage:imageNormal forState:UIControlStateNormal];
[checkbox setImage:imageSelected forState:UIControlStateSelected];
[checkbox addTarget:self action:@selector(selectCheckbox:) forControlEvents:UIControlEventTouchUpInside];
checkbox;
});
[self setCheckboxSelected:_checkboxSelected];
}
- (UIView *)leftView
{
return self.checkbox;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNImageLine.h
================================================
//
// LMNImageLine.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLine.h"
@interface LMNImageLine : LMNLine
@property (nonatomic, strong, readonly) UIImage *image;
@property (nonatomic, readonly) LMNImageView *bindingImageView;
- (void)bindImageView:(LMNImageView *)bindingImageView;
- (void)unbindImageView;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNImageLine.m
================================================
//
// LMNImageLine.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNImageLine.h"
#import "LMNImageView.h"
#import "LMNStore.h"
#import "UIImage+LMNStore.h"
@interface LMNImageLine ()
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, copy) NSString *imageFile;
@end
@implementation LMNImageLine
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.imageFile = [aDecoder decodeObjectForKey:@"imageFile"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder:aCoder];
UIImage *image = self.bindingImageView.image;
if (image) {
if (!image.lmn_fileName) {
NSData *imageData = UIImagePNGRepresentation(image);
NSString *fileName = [NSUUID UUID].UUIDString;
NSURL *fileURL = [[LMNStore shared].imageDirectory URLByAppendingPathComponent:fileName];
[imageData writeToURL:fileURL atomically:YES];
image.lmn_fileName = fileName;
}
[aCoder encodeObject:image.lmn_fileName forKey:@"imageFile"];
}
}
- (void)bindImageView:(LMNImageView *)bindingImageView
{
_bindingImageView = bindingImageView;
bindingImageView.owner = self;
}
- (void)unbindImageView
{
_bindingImageView.owner = nil;
_bindingImageView = nil;
}
- (UIImage *)image
{
if (!_image) {
_image = self.bindingImageView.image;
}
if (!_image && self.imageFile != nil) {
NSURL *fileURL = [[LMNStore shared].imageDirectory URLByAppendingPathComponent:self.imageFile];
_image = [UIImage imageWithContentsOfFile:fileURL.path];
_image.lmn_fileName = self.imageFile;
}
return _image;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLine.h
================================================
//
// LMNTextLine.h
// LMNote
//
// Created by littleMeaning on 2018/3/16.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "LMNLineModes.h"
@class LMNImageView;
@class LMNLineChain;
@interface LMNLine : NSObject <NSCoding>
@property (nonatomic, assign, readonly) BOOL isRoot;
@property (nonatomic, weak, readonly) LMNLineChain *lineChain;
- (void)makeRootOfLineChain:(LMNLineChain *)lineChain;
@property (nonatomic, copy, readonly) NSString *uuid;
@property (nonatomic, assign) NSRange range;
@property (nonatomic, weak, readonly) LMNLine *prev;
@property (nonatomic, strong) LMNLine *next;
- (NSDictionary *)attributes;
- (NSMutableDictionary *)attributesWithFont:(UIFont *)font;
- (void)insteadOfLine:(LMNLine *)line;
- (void)inheritFromLine:(LMNLine *)line;
- (void)clean;
@end
@interface LMNLine (Mode)
@property (nonatomic, readonly) LMNLineMode mode;
- (BOOL)isKindOfMode:(LMNLineMode)mode;
+ (instancetype)line;
+ (instancetype)lineWithMode:(LMNLineMode)mode;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLine.m
================================================
//
// LMNTextLine.m
// LMNote
//
// Created by littleMeaning on 2018/3/16.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLine.h"
#import "UIFont+LMNote.h"
#import "LMNLineChain.h"
LMNLineMode const LMNLineModeContent = @"content";
LMNLineMode const LMNLineModeTitle = @"title";
LMNLineMode const LMNLineModeSubTitle = @"subtitle";
LMNLineMode const LMNLineModeNumbering = @"numbering";
LMNLineMode const LMNLineModeBullets = @"bullets";
LMNLineMode const LMNLineModeCheckbox = @"checkbox";
LMNLineMode const LMNLineModeModeImage = @"image";
LMNLineMode const LMNLineModeModeUnknown = @"Unknown";
static Class lmn_lineClassOf(LMNLineMode mode)
{
NSString *className = [NSString stringWithFormat:@"LMN%@Line", [mode capitalizedString]];
return NSClassFromString(className);
}
@interface LMNLine ()
@property (nonatomic, assign) BOOL isRoot;
@property (nonatomic, weak) LMNLineChain *lineChain;
@property (nonatomic, copy, readwrite) NSString *uuid;
@property (nonatomic, weak, readwrite) LMNLine *prev;
@end
@implementation LMNLine
static CGFloat kLineSpacing = 2.f;
- (instancetype)init
{
self = [super init];
if (self) {
_uuid = [NSUUID UUID].UUIDString;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.uuid = [aDecoder decodeObjectForKey:@"uuid"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.uuid forKey:@"uuid"];
}
#pragma mark - getter & setter
- (void)setNext:(LMNLine *)next
{
_next = next;
next.prev = self;
}
- (void)makeRootOfLineChain:(LMNLineChain *)lineChain
{
self.isRoot = YES;
self.lineChain = lineChain;
lineChain.rootLine = self;
}
#pragma mark - attributes for paragraph modes
- (NSMutableDictionary *)attributesWithFont:(UIFont *)font
{
static UIFont *defaultFont;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultFont = [UIFont fontWithFontSize:17.f bold:NO italic:NO];
});
if (!font) {
font = defaultFont;
}
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
attributes[NSForegroundColorAttributeName] = [UIColor grayColor];
attributes[NSFontAttributeName] = font;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = kLineSpacing;
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
return attributes;
}
- (NSDictionary *)attributes
{
return nil;
}
- (void)insteadOfLine:(LMNLine *)line
{
self.range = line.range;
self.next = line.next;
line.prev.next = self;
if (line.isRoot) {
[self makeRootOfLineChain:line.lineChain];
}
[line clean];
}
- (void)inheritFromLine:(LMNLine *)line {}
- (void)clean {}
- (void)dealloc {
[self clean];
}
@end
@implementation LMNLine (Mode)
#pragma mark - mode
+ (instancetype)line
{
return [self lineWithMode:LMNLineModeContent];
}
+ (instancetype)lineWithMode:(LMNLineMode)mode
{
return [lmn_lineClassOf(mode) new];
}
- (BOOL)isKindOfMode:(LMNLineMode)mode
{
Class cls = lmn_lineClassOf(mode);
return [self isKindOfClass:cls];
}
- (LMNLineMode)mode
{
NSArray *modes = @[
LMNLineModeContent,
LMNLineModeTitle,
LMNLineModeSubTitle,
LMNLineModeNumbering,
LMNLineModeBullets,
LMNLineModeCheckbox,
LMNLineModeModeImage,
];
for (LMNLineMode mode in modes) {
if ([self isKindOfMode:mode]) {
return mode;
}
}
return LMNLineModeModeUnknown;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLineChain+Numbering.h
================================================
//
// LMNLineChain+Numbering.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLineChain.h"
@interface LMNLineChain (Numbering)
- (void)updateNumberings;
- (void)updateNumberingStartWithLine:(LMNLine *)line;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLineChain+Numbering.m
================================================
//
// LMNLineChain+Numbering.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLineChain+Numbering.h"
#import "LMNNumberingLine.h"
#import "LMNNumberingLine.h"
@implementation LMNLineChain (Numbering)
- (void)updateNumberings
{
// 将连续数字段落的第一个节点提取出来。
id headLine = nil;
NSMutableArray *numberingLines = [NSMutableArray array];
LMNLine *line = self.rootLine;
do {
BOOL isNumbering = [line isKindOfClass:[LMNNumberingLine class]];
if (isNumbering && headLine == nil) {
headLine = line;
}
else if (!isNumbering && headLine != nil) {
[numberingLines addObject:headLine];
headLine = nil;
}
line = line.next;
}
while (line);
if (headLine) {
[numberingLines addObject:headLine];
headLine = nil;
}
for (LMNLine *line in numberingLines) {
[self updateNumberingStartWithLine:line];
}
}
- (void)updateNumberingStartWithLine:(LMNLine *)line
{
if (![line isKindOfClass:[LMNNumberingLine class]]) {
return;
}
NSUInteger offset = 0;
if ([line.prev isKindOfClass:[LMNNumberingLine class]]) {
offset += ((LMNNumberingLine *)line.prev).number;
}
NSMutableArray *lines = [NSMutableArray array];
while (line && [line isKindOfClass:[LMNNumberingLine class]]) {
[lines addObject:line];
line = line.next;
}
[lines enumerateObjectsUsingBlock:^(LMNLine *obj, NSUInteger idx, BOOL *stop) {
[(LMNNumberingLine *)obj setNumber:idx + 1 + offset];
}];
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLineChain.h
================================================
//
// LMNLineChain.h
// LMNote
//
// Created by littleMeaning on 2018/3/17.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LMNLine.h"
@class LMNImageView;
@interface LMNLineChain : NSObject <NSCoding>
@property (nonatomic, readonly) NSString *text;
- (void)updateWithText:(NSString *)text;
@property (nonatomic, strong) LMNLine *rootLine;
- (LMNLine *)lineAtLocation:(NSUInteger)loc;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLineChain.m
================================================
//
// LMNLineChain.m
// LMNote
//
// Created by littleMeaning on 2018/3/17.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLineChain.h"
#import "LMNImageView.h"
@interface LMNLineChain ()
@property (nonatomic, copy) NSString *text;
@end
@implementation LMNLineChain
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
NSArray *lines = [aDecoder decodeObjectForKey:@"lines"];
if (lines.count == 0) {
return nil;
}
LMNLine *tail = nil;
for (LMNLine *line in lines) {
if (tail) {
tail.next = line;
}
else {
[line makeRootOfLineChain:self];
}
tail = line;
}
NSString *text = [aDecoder decodeObjectForKey:@"text"];
[self updateWithText:text];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
NSMutableArray *lines = [NSMutableArray array];
LMNLine *line = self.rootLine;
do {
[lines addObject:line];
} while ((line = line.next));
[aCoder encodeObject:lines forKey:@"lines"];
[aCoder encodeObject:self.text forKey:@"text"];
}
- (instancetype)init
{
self = [super init];
if (self) {
LMNLine *line = [LMNLine lineWithMode:LMNLineModeContent];
[line makeRootOfLineChain:self];
}
return self;
}
- (void)updateWithText:(NSString *)text
{
_text = [text copy];
NSRange range = NSMakeRange(0, 0);
LMNLine *line = self.rootLine;
while (YES) {
range = [text paragraphRangeForRange:range];
line.range = range;
range.location = NSMaxRange(range);
range.length = 0;
if (line.next) {
line = line.next;
if (range.location >= text.length) {
line.range = range;
break;
}
}
else {
break;
}
}
}
- (LMNLine *)lineAtLocation:(NSUInteger)loc
{
LMNLine *line = self.rootLine;
while (line) {
if (NSLocationInRange(loc, line.range)) {
return line;
}
if (line.next == nil && self.text.length == loc) {
return line;
}
line = line.next;
}
return nil;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNLineModes.h
================================================
//
// LMNLineModes.h
// LMNote
//
// Created by littleMeaning on 2018/7/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#ifndef LMNLineModes_h
#define LMNLineModes_h
typedef NSString * LMNLineMode;
extern LMNLineMode const LMNLineModeContent;
extern LMNLineMode const LMNLineModeTitle;
extern LMNLineMode const LMNLineModeSubTitle;
extern LMNLineMode const LMNLineModeNumbering;
extern LMNLineMode const LMNLineModeBullets;
extern LMNLineMode const LMNLineModeCheckbox;
extern LMNLineMode const LMNLineModeModeImage;
extern LMNLineMode const LMNLineModeModeUnknown;
#endif /* LMNLineModes_h */
================================================
FILE: LMNote/LMNote/Lines/LMNNumberingLine.h
================================================
//
// LMNNumberingLine.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNSpecialLine.h"
@interface LMNNumberingLine : LMNSpecialLine
@property (nonatomic, assign) NSUInteger number;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNNumberingLine.m
================================================
//
// LMNNumberingLine.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNNumberingLine.h"
#import "UIFont+LMNote.h"
@interface LMNNumberingLine ()
@property (nonatomic, strong) UILabel *numberLabel;
@end
@implementation LMNNumberingLine
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.number = [aDecoder decodeIntegerForKey:@"number"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeInteger:self.number forKey:@"number"];
}
#pragma mark - number
- (void)setNumber:(NSUInteger)number
{
_number = number;
self.numberLabel.text = [@(number).stringValue stringByAppendingString:@" ."];
self.numberLabel.attributedText = ({
NSMutableAttributedString *attributedText = [self.numberLabel.attributedText mutableCopy];
[attributedText addAttribute:NSBaselineOffsetAttributeName value:@(-0.5f) range:NSMakeRange(0, attributedText.length)];
attributedText;
});
}
#pragma - left view
- (CGSize)intrinsicLeftSize
{
CGFloat width = LMNSpecialLineHeight;
if (self.numberLabel) {
[self.numberLabel sizeToFit];
width = MAX(CGRectGetWidth(self.numberLabel.frame), LMNSpecialLineHeight);
}
return CGSizeMake(width, CGRectGetHeight(self.numberLabel.frame));
}
- (void)loadLeftView
{
if (self.numberLabel) {
return;
}
self.numberLabel = ({
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont fontWithFontSize:17.f bold:NO italic:NO];
label.textAlignment = NSTextAlignmentRight;
label.textColor = [UIColor grayColor];
label;
});
self.number = self.number;
}
- (UIView *)leftView
{
return self.numberLabel;
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNSpecialLine.h
================================================
//
// LMNSpecialLine.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLine.h"
extern CGFloat const LMNSpecialLineHeight;
@interface LMNSpecialLine : LMNLine
@property (nonatomic, readonly) UIView *leftView;
- (CGSize)intrinsicLeftSize;
- (void)loadLeftView;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNSpecialLine.m
================================================
//
// LMNSpecialLine.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNSpecialLine.h"
@implementation LMNSpecialLine
CGFloat const LMNSpecialLineHeight = 26.f;
- (NSDictionary *)attributes
{
static NSDictionary *attributes;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
attributes = [self attributesWithFont:nil];
UIFont *font = attributes[NSFontAttributeName];
CGFloat paragraphSpacing = (LMNSpecialLineHeight - roundf(font.lineHeight)) / 2.f;
NSMutableParagraphStyle *paragraphStyle = attributes[NSParagraphStyleAttributeName];
paragraphStyle.paragraphSpacing = paragraphSpacing;
paragraphStyle.paragraphSpacingBefore = paragraphSpacing;
attributes = [attributes copy];
});
return attributes;
}
- (CGSize)intrinsicLeftSize
{
return CGSizeMake(LMNSpecialLineHeight, LMNSpecialLineHeight);
}
- (void)loadLeftView {}
- (void)clean
{
[self.leftView removeFromSuperview];
[super clean];
}
@end
================================================
FILE: LMNote/LMNote/Lines/LMNTextLine.h
================================================
//
// LMNTextLine.h
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNLine.h"
@interface LMNTextLine : LMNLine
@property (nonatomic, assign) NSTextAlignment textAlignment;
@end
================================================
FILE: LMNote/LMNote/Lines/LMNTextLine.m
================================================
//
// LMNTextLine.m
// LMNote
//
// Created by littleMeaning on 2018/7/9.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNTextLine.h"
#import "UIFont+LMNote.h"
@interface LMNTextLine ()
@property (nonatomic, readonly) UIFont *textFont;
@end
@implementation LMNTextLine
- (UIFont *)textFont
{
return nil;
}
- (NSDictionary *)attributes
{
NSMutableDictionary *attributes = [self attributesWithFont:self.textFont];
NSMutableParagraphStyle *paragraphStyle = attributes[NSParagraphStyleAttributeName];
paragraphStyle.alignment = self.textAlignment;
return attributes;
}
- (void)inheritFromLine:(LMNLine *)line
{
[super inheritFromLine:line];
if ([line isKindOfClass:[LMNTextLine class]]) {
self.textAlignment = ((LMNTextLine *)line).textAlignment;
}
}
@end
#pragma mark -
@interface LMNContentLine : LMNTextLine
@end
@implementation LMNContentLine
- (UIFont *)textFont
{
return [UIFont fontWithFontSize:17.f bold:NO italic:NO];
}
@end
#pragma mark -
@interface LMNTitleLine : LMNTextLine
@end
@implementation LMNTitleLine
- (UIFont *)textFont
{
return [UIFont fontWithFontSize:24.f bold:YES italic:NO];
}
@end
#pragma mark -
@interface LMNSubtitleLine : LMNTextLine
@end
@implementation LMNSubtitleLine
- (UIFont *)textFont
{
return [UIFont fontWithFontSize:20.f bold:YES italic:NO];
}
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNImageInputViewController.h
================================================
//
// LMNImageInputViewController.h
// LMNote
//
// Created by littleMeaning on 2018/4/19.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@import Photos;
@class LMNImageInputViewController;
@protocol LMNImageInputViewControllerDelegate <NSObject>
- (void)lmn_imageInput:(LMNImageInputViewController *)viewController didSelectPHAsset:(PHAsset *)asset;
- (void)lmn_imageInputClose:(LMNImageInputViewController *)viewController;
@end
@interface LMNImageInputViewController : UIViewController
@property (nonatomic, weak) id<LMNImageInputViewControllerDelegate> delegate;
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNImageInputViewController.m
================================================
//
// LMNImageInputViewController.m
// LMNote
//
// Created by littleMeaning on 2018/4/19.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNImageInputViewController.h"
#import "LMNPhotoCollectionCell.h"
@import Photos;
@interface LMNImageInputViewController () <UICollectionViewDataSource, UICollectionViewDelegate, PHPhotoLibraryChangeObserver, UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIImageView *headerImageView;
@property (nonatomic, strong) UILabel *headerLabel;
@property (nonatomic, strong) UIButton *closeButton;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) PHFetchResult *photosResult;
@property (nonatomic, strong) NSIndexPath *selectedIndexPath;
@end
@implementation LMNImageInputViewController
static NSString * kReuseIdentifier = @"photo";
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
[self loadSubviews];
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}
- (void)loadSubviews
{
self.headerImageView = ({
UIImageView *view = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"lmn_tool_image"]];
view.contentMode = UIViewContentModeCenter;
view;
});
self.headerLabel = ({
UILabel *label = [[UILabel alloc] init];
label.textColor = [UIColor colorWithWhite:0.1 alpha:1.f];
label.font = [UIFont systemFontOfSize:15.f];
label.text = @"照片";
label;
});
self.closeButton = ({
UIImage *image = [UIImage imageNamed:@"lmn_tool_close"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:image forState:UIControlStateNormal];
button;
});
[self.closeButton addTarget:self action:@selector(close:) forControlEvents:UIControlEventTouchUpInside];
self.headerView = ({
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.f];
view;
});
[self.headerView addSubview:self.headerImageView];
[self.headerView addSubview:self.headerLabel];
[self.headerView addSubview:self.closeButton];
[self.view addSubview:self.headerView];
self.collectionView = ({
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *view = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
view.backgroundColor = self.headerView.backgroundColor;
view.dataSource = self;
view.delegate = self;
view;
});
[self.view addSubview:self.collectionView];
[self.collectionView registerClass:[LMNPhotoCollectionCell class] forCellWithReuseIdentifier:kReuseIdentifier];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect rect = self.view.bounds;
rect.size.height = 44.f;
self.headerView.frame = rect;
self.headerImageView.frame = CGRectMake(0, 0, 44.f, 44.f);
self.headerLabel.frame = CGRectMake(44, 0, 100.f, 44.f);
self.closeButton.frame = CGRectMake(CGRectGetWidth(self.headerView.bounds) - 52.f, 0, 44.f, 44.f);
static CGFloat kSpacing = 2.f;
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
layout.minimumLineSpacing = kSpacing;
layout.minimumInteritemSpacing = kSpacing;
CGFloat width = CGRectGetWidth(self.view.bounds) / 3.f - kSpacing;
layout.itemSize = CGSizeMake(width, width);
rect.origin.y = CGRectGetHeight(self.headerView.frame);
rect.size.height = CGRectGetHeight(self.view.bounds) - rect.origin.y;
self.collectionView.frame = rect;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.photosResult.count == 0) {
[self fetchPhotos];
}
else {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (self.selectedIndexPath) {
[self.collectionView deselectItemAtIndexPath:self.selectedIndexPath animated:NO];
self.selectedIndexPath = nil;
}
}
- (void)dealloc {
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}
- (void)fetchPhotos {
PHFetchOptions *nearestPhotosOptions = [[PHFetchOptions alloc] init];
nearestPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
self.photosResult = [PHAsset fetchAssetsWithOptions:nearestPhotosOptions];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
- (void)reload {
self.selectedIndexPath = nil;
[self.collectionView reloadData];
}
- (void)close:(id)sender
{
if ([self.delegate respondsToSelector:@selector(lmn_imageInputClose:)]) {
[self.delegate lmn_imageInputClose:self];
}
}
#pragma mark - <PHPhotoLibraryChangeObserver>
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
[self fetchPhotos];
}
#pragma mark - <UICollectionViewDataSource, UICollectionViewDelegate>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.photosResult.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
LMNPhotoCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
PHAsset *asset = self.photosResult[indexPath.item];
[cell setAsset:asset];
if (!cell.handler) {
cell.handler = ^(PHAsset *asset) {
[self.delegate lmn_imageInput:self didSelectPHAsset:asset];
};
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (self.selectedIndexPath == indexPath) {
return;
}
if (self.selectedIndexPath) {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:self.selectedIndexPath];
[(LMNPhotoCollectionCell *)cell performSelectionAnimations];
}
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[(LMNPhotoCollectionCell *)cell performSelectionAnimations];
self.selectedIndexPath = indexPath;
}
#pragma mark - <UIImagePickerControllerDelegate>
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
CGSize targetSize = [UIScreen mainScreen].bounds.size;
targetSize.width *= 2;
targetSize.height = targetSize.width * originalImage.size.height / originalImage.size.width;
UIGraphicsBeginImageContext(targetSize);
[originalImage drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
__unused UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNImageView.h
================================================
//
// LMNImageView.h
// LMNote
//
// Created by littleMeaning on 2018/4/24.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@class LMNImageView;
@class LMNImageLine;
@protocol LMNImageViewDelegate <NSObject>
@optional
- (void)lmn_imageViewBeginEditing:(LMNImageView *)imageView;
- (void)lmn_imageViewEndEditing:(LMNImageView *)imageView;
- (void)lmn_imageViewDelete:(LMNImageView *)imageView;
@end
@interface LMNImageView : UIView
@property (nonatomic, weak) id<LMNImageViewDelegate> delegate;
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) UIBezierPath *exclusionPath;
@property (nonatomic, readonly) BOOL editing;
- (instancetype)initWithImage:(UIImage *)image;
- (void)beginEditing;
- (void)endEditing;
@property (nonatomic, weak) LMNImageLine *owner;
- (void)unbindFromOwner;
+ (CGSize)sizeThatFit:(UIImage *)image limitWidth:(CGFloat)limitWidth;
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNImageView.m
================================================
//
// LMNImageView.m
// LMNote
//
// Created by littleMeaning on 2018/4/24.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNImageView.h"
#import "LMNImageLine.h"
@interface LMNImageView ()
@property (nonatomic, strong) UIControl *borderView;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIButton *deleteButton;
@property (nonatomic, strong) UIVisualEffectView *blurEffectView;
@property (nonatomic, assign) BOOL editing;
@end
@implementation LMNImageView
static CGFloat const kMargin = 5.f;
static CGFloat const kVerticalInset = 5.f;
static CGFloat const kVerticalMargin = kVerticalInset + kMargin;
+ (CGSize)sizeThatFit:(UIImage *)image limitWidth:(CGFloat)limitWidth
{
if (!image) {
return CGSizeZero;
}
CGFloat height = (limitWidth - kMargin * 2) * image.size.height / image.size.width + kVerticalMargin * 2;
return CGSizeMake(limitWidth, roundf(height));
}
- (instancetype)initWithImage:(UIImage *)image
{
self = [self initWithFrame:CGRectZero];
if (self) {
self.backgroundColor = [UIColor whiteColor];
_borderView = [[UIControl alloc] init];
_borderView.layer.borderColor = [UIColor colorWithWhite:0.8f alpha:1.f].CGColor;
_borderView.layer.borderWidth = 0.5f;
_borderView.layer.cornerRadius = 2.f;
[self addSubview:_borderView];
_image = image;
_imageView = [[UIImageView alloc] initWithImage:image];
_imageView.clipsToBounds = YES;
_imageView.contentMode = UIViewContentModeScaleAspectFill;
[_borderView addSubview:_imageView];
_deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_deleteButton setImage:[UIImage imageNamed:@"lmn_delete"] forState:UIControlStateNormal];
_deleteButton.hidden = YES;
[self addSubview:_deleteButton];
[_borderView addTarget:self action:@selector(tapContainer:) forControlEvents:UIControlEventTouchUpInside];
[_deleteButton addTarget:self action:@selector(delete:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.borderView.frame = CGRectInset(self.bounds, 0, kVerticalInset);
self.imageView.frame = CGRectInset(self.borderView.bounds, kMargin, kMargin);
self.deleteButton.frame = CGRectMake(kMargin + 5.f, kVerticalMargin + 5.f, 34.f, 34.f);
}
- (void)setImage:(UIImage *)image
{
_image = image;
self.imageView.image = image;
[self setNeedsDisplay];
}
#pragma mark - editing
- (void)setEditing:(BOOL)editing
{
if (_editing == editing) {
return;
}
_editing = editing;
if (!self.blurEffectView) {
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurEffectView.userInteractionEnabled = NO;
_blurEffectView = blurEffectView;
}
}
if (!self.blurEffectView) {
self.deleteButton.hidden = !editing;
return;
}
self.deleteButton.hidden = !editing;
if (editing) {
self.blurEffectView.frame = self.imageView.frame;
[self.borderView addSubview:self.blurEffectView];
self.deleteButton.alpha = 0;
self.blurEffectView.alpha = 0;
[UIView animateWithDuration:0.25 animations:^{
self.blurEffectView.alpha = 1;
self.deleteButton.alpha = 1;
}];
}
else {
[UIView animateWithDuration:0.25 animations:^{
self.blurEffectView.alpha = 0;
self.deleteButton.alpha = 0;
} completion:^(BOOL finished) {
[self.blurEffectView removeFromSuperview];
}];
}
}
- (void)beginEditing
{
self.editing = YES;
if ([self.delegate respondsToSelector:@selector(lmn_imageViewBeginEditing:)]) {
[self.delegate lmn_imageViewBeginEditing:self];
}
}
- (void)endEditing
{
self.editing = NO;
if ([self.delegate respondsToSelector:@selector(lmn_imageViewEndEditing:)]) {
[self.delegate lmn_imageViewEndEditing:self];
}
}
#pragma mark - actions
- (void)tapContainer:(id)sender
{
if (self.editing) {
[self endEditing];
}
else {
[self beginEditing];
}
}
- (void)delete:(id)sender
{
if ([self.delegate respondsToSelector:@selector(lmn_imageViewDelete:)]) {
[self.delegate lmn_imageViewDelete:self];
}
}
#pragma mark -
- (void)unbindFromOwner
{
if (self.owner.bindingImageView == self) {
[self.owner unbindImageView];
}
}
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNPhotoCollectionCell.h
================================================
//
// LMPhotoCollectionCell.h
// SimpleWord
//
// Created by littleMeaning on 16/5/16.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@class PHAsset;
@interface LMNPhotoCollectionCell : UICollectionViewCell
@property (nonatomic, copy) void (^handler)(PHAsset *asset);
- (void)setAsset:(PHAsset *)asset;
- (void)performSelectionAnimations;
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNPhotoCollectionCell.m
================================================
//
// LMPhotoCollectionCell.m
// SimpleWord
//
// Created by littleMeaning on 16/5/16.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import "LMNPhotoCollectionCell.h"
@import Photos;
@interface LMNPhotoCollectionCell ()
@property (nonatomic, strong) PHAsset *asset;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIView *bezelView;
@property (nonatomic, strong) UIButton *useButton;
@end
@implementation LMNPhotoCollectionCell
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.clipsToBounds = YES;
_imageView = [[UIImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
[self.contentView addSubview:_imageView];
_bezelView = [[UIView alloc] init];
_bezelView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];
_bezelView.layer.borderColor = [UIColor colorWithWhite:0.98 alpha:1.f].CGColor;
_bezelView.layer.borderWidth = 2.f;
_bezelView.hidden = YES;
[self.contentView addSubview:_bezelView];
_useButton = [UIButton buttonWithType:UIButtonTypeCustom];
_useButton.titleLabel.font = [UIFont systemFontOfSize:15.f];
_useButton.layer.borderColor = [UIColor whiteColor].CGColor;
_useButton.layer.borderWidth = 1.f;
_useButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3f];
[_useButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_useButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateHighlighted];
[_useButton setTitle:@"使用" forState:UIControlStateNormal];
[_bezelView addSubview:_useButton];
[_useButton addTarget:self action:@selector(useAction:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.imageView.frame = self.bounds;
self.bezelView.frame = self.bounds;
CGRect rect = CGRectMake(0, 0, 60.f, 36.f);
rect.origin.x = (CGRectGetWidth(self.bounds) - CGRectGetWidth(rect)) / 2.f;
rect.origin.y = (CGRectGetHeight(self.bounds) - CGRectGetHeight(rect)) / 2.f;
self.useButton.frame = rect;
self.useButton.layer.cornerRadius = CGRectGetHeight(rect) / 2.f;
}
- (void)setSelected:(BOOL)selected
{
// TODO: 在滑动的时候会触发,导致闪烁。
[super setSelected:selected];
self.bezelView.hidden = !selected;
}
- (void)performSelectionAnimations
{
CGFloat scale = self.selected ? 1.1f : 1.f;
self.bezelView.alpha = !self.selected;
self.bezelView.hidden = NO;
[UIView animateWithDuration:0.25 animations:^{
self.bezelView.alpha = self.selected;
self.imageView.transform = CGAffineTransformMakeScale(scale, scale);
} completion:^(BOOL finished) {
if (!self.selected) {
self.bezelView.hidden = YES;
}
}];
}
- (void)setAsset:(PHAsset *)asset {
_asset = asset;
if (!asset) {
return;
}
CGFloat imageWidth = CGRectGetWidth([UIScreen mainScreen].bounds) / 3.f;
CGSize targetSize = CGSizeMake(imageWidth, imageWidth);
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFit options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
if (self.asset == asset) {
self.imageView.image = result;
}
}];
}
- (void)prepareForReuse
{
self.imageView.image = nil;
self.asset = nil;
self.bezelView.hidden = YES;
}
- (void)useAction:(UIButton *)sender
{
self.handler(self.asset);
}
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNToolBar.h
================================================
//
// LMNToolBar.h
// LMNote
//
// Created by littleMeaning on 2018/4/17.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "LMNLineModes.h"
extern NSString * const LMFontBoldAttributeName;
extern NSString * const LMFontUnderlineAttributeName;
extern NSString * const LMFontItalicAttributeName;
extern NSString * const LMFontStrikethroughAttributeName;
extern NSString * const LMLineModeAttributeName;
typedef NS_ENUM(NSUInteger, LMNToolBarItemTag) {
LMNToolBarItemTagImage = 1001,
};
@class LMNToolBar;
extern NSInteger const LMNToolBarIndexOfCloseItem;
@protocol LMNToolBarDelegate <NSObject>
- (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedMode:(LMNLineMode)mode;
- (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedAttributes:(NSDictionary *)attributes;
- (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedTextAlignment:(NSTextAlignment)alignment;
- (void)lmn_toolBar:(LMNToolBar *)toolBar didSelectedItemWithTag:(LMNToolBarItemTag)tag;
- (void)lmn_toolBarClose:(LMNToolBar *)toolBar;
@end
@interface LMNToolBar : UIView
@property (nonatomic, weak) id<LMNToolBarDelegate> delegate;
@property (nonatomic, copy) LMNLineMode mode;
+ (instancetype)toolBar;
- (void)showSubToolBar:(BOOL)animated;
- (void)hideSubToolBar:(BOOL)animated;
- (void)reloadDataWithTypingAttributes:(NSDictionary *)typingAttributes mode:(LMNLineMode)mode isMultiLine:(BOOL)isMultiLine;
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNToolBar.m
================================================
//
// LMNToolBar.m
// LMNote
//
// Created by littleMeaning on 2018/4/17.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNToolBar.h"
#import "UIFont+LMNote.h"
NSString * const LMFontBoldAttributeName = @"bold";
NSString * const LMFontUnderlineAttributeName = @"underline";
NSString * const LMFontItalicAttributeName = @"italic";
NSString * const LMFontStrikethroughAttributeName = @"strikethrough";
NSString * const LMLineModeAttributeName = @"mode";
UIImage *lmn_getRectangleImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, CGFloat cornerRadius, CGFloat margin, UIColor *color)
{
UIImage *image;
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
{
CGRect rect = CGRectZero;
rect.size = size;
rect = CGRectInset(rect, margin, margin);
CAShapeLayer *layer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius];
layer.path = path.CGPath;
layer.lineWidth = 0;
layer.fillColor = color.CGColor;
[layer renderInContext:UIGraphicsGetCurrentContext()];
}
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@interface LMNToolBar ()
@property (nonatomic, strong) UIView *toolBar;
@property (nonatomic, strong) UIView *subToolBar;
@property (nonatomic, strong) UIButton *subToolBarLeftView;
@property (nonatomic, copy) NSArray<UIButton *> *itemButtons;
@property (nonatomic, copy) NSArray<UIButton *> *subItemButtons;
@property (nonatomic, readonly) UIButton *fontButton;
@property (nonatomic, readonly) UIButton *titleButton;
@property (nonatomic, readonly) UIButton *listButton;
@property (nonatomic, readonly) UIButton *checkboxButton;
@property (nonatomic, readonly) UIButton *alignmentButton;
@property (nonatomic, readonly) UIButton *imageButton;
@property (nonatomic, readonly) UIButton *dismissButton;
@property (nonatomic, readonly) UIButton *boldButton;
@property (nonatomic, readonly) UIButton *italicButton;
@property (nonatomic, readonly) UIButton *underlineButton;
@property (nonatomic, readonly) UIButton *strikethroughButton;
@property (nonatomic, readonly) UIButton *foldButton;
@property (nonatomic, assign) NSTextAlignment alignment;
@end
@implementation LMNToolBar
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self loadSubviews];
}
return self;
}
+ (instancetype)toolBar
{
return [[self alloc] initWithFrame:CGRectZero];
}
- (void)loadToolBar
{
if (!_toolBar) {
_toolBar = [[UIView alloc] init];
_toolBar.backgroundColor = [UIColor whiteColor];
NSMutableArray *itemButtons = [NSMutableArray array];
NSArray *images = @[
[UIImage imageNamed:@"lmn_tool_a"],
[UIImage imageNamed:@"lmn_tool_t"],
[UIImage imageNamed:@"lmn_list"],
[UIImage imageNamed:@"lmn_list_checkbox"],
[UIImage imageNamed:@"lmn_alignment_left"],
[UIImage imageNamed:@"lmn_tool_image"],
[UIImage imageNamed:@"lmn_tool_close"]
];
for (int idx = 0; idx < images.count; idx ++) {
UIImage *image = images[idx];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:image forState:UIControlStateNormal];
[_toolBar addSubview:button];
[itemButtons addObject:button];
[button addTarget:self
action:@selector(didSelectItem:)
forControlEvents:UIControlEventTouchUpInside];
switch (idx) {
case 0:
_fontButton = button;
break;
case 1:
_titleButton = button;
break;
case 2:
_listButton = button;
break;
case 3:
_checkboxButton = button;
break;
case 4:
_alignmentButton = button;
break;
case 5:
button.tag = LMNToolBarItemTagImage;
_imageButton = button;
break;
case 6:
_dismissButton = button;
break;
default:
break;
}
}
self.itemButtons = itemButtons;
}
}
- (void)loadSubToolBar
{
static UIImage *bgImage;
static UIImage *bgImageSelected;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bgImage = lmn_getRectangleImageWithOptions(CGSizeMake(44.f, 44.f), NO, [UIScreen mainScreen].scale, 4.f, 7.f, [UIColor colorWithWhite:230/255.f alpha:1.f]);
bgImageSelected = lmn_getRectangleImageWithOptions(CGSizeMake(44.f, 44.f), NO, [UIScreen mainScreen].scale, 4.f, 7.f, [UIColor colorWithRed:43/255.f green:132/255.f blue:210/255.f alpha:1.f]);
});
_subToolBar = [[UIView alloc] init];
_subToolBar.backgroundColor = [UIColor whiteColor];
NSMutableArray *itemButtons = [NSMutableArray array];
NSArray *images = @[
[UIImage imageNamed:@"lmn_font_bold"],
[UIImage imageNamed:@"lmn_font_italic"],
[UIImage imageNamed:@"lmn_font_underline"],
[UIImage imageNamed:@"lmn_font_strikethrough"],
[UIImage imageNamed:@"lmn_left_square"]
];;
for (int idx = 0; idx < images.count; idx ++) {
UIImage *image = images[idx];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:image forState:UIControlStateNormal];
if (idx != 4) {
[button setBackgroundImage:bgImage forState:UIControlStateNormal];
[button setBackgroundImage:bgImageSelected forState:UIControlStateSelected];
}
[_subToolBar addSubview:button];
[itemButtons addObject:button];
[button addTarget:self
action:@selector(didSelectItem:)
forControlEvents:UIControlEventTouchUpInside];
switch (idx) {
case 0:
_boldButton = button;
break;
case 1:
_italicButton = button;
break;
case 2:
_underlineButton = button;
break;
case 3:
_strikethroughButton = button;
break;
case 4:
_foldButton = button;
default:
break;
}
}
self.subItemButtons = itemButtons;
}
- (void)loadSubviews
{
[self loadToolBar];
[self loadSubToolBar];
UIImage *image = [self.fontButton imageForState:UIControlStateNormal];
self.subToolBarLeftView = [UIButton buttonWithType:UIButtonTypeCustom];
self.subToolBarLeftView.backgroundColor = [UIColor whiteColor];
[self.subToolBarLeftView setImage:image forState:UIControlStateNormal];
self.subToolBarLeftView.hidden = YES;
self.subToolBar.hidden = YES;
[self addSubview:self.toolBar];
[self addSubview:self.subToolBarLeftView];
[self addSubview:self.subToolBar];
[self.subToolBarLeftView addTarget:self action:@selector(didSelectItem:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)layoutSubviews
{
[super layoutSubviews];
{
self.toolBar.frame = self.bounds;
CGRect rect = self.toolBar.bounds;
rect.size.width = CGRectGetHeight(rect);
rect.origin.x = 8.f;
for (UIButton *button in self.itemButtons) {
if (button == self.itemButtons.lastObject) {
rect.origin.x = CGRectGetWidth(self.toolBar.bounds) - CGRectGetWidth(rect) - 8.f;
}
button.frame = rect;
rect.origin.x += CGRectGetWidth(rect) + 4.f;
}
}
CGFloat height = CGRectGetHeight(self.bounds);
self.subToolBarLeftView.frame = CGRectMake(0, 0, height, height);
{
self.subToolBar.frame = ({
CGRect rect = self.bounds;
rect.size.width -= height;
rect.origin.x = height;
rect;
});
CGRect rect = self.subToolBar.bounds;
rect.size.width = CGRectGetHeight(rect);
rect.origin.x = 8.f;
for (UIButton *button in self.subItemButtons) {
// 隐藏斜体按钮,"PingFang SC" 字体不支持斜体。
if (button == self.italicButton) {
button.hidden = YES;
continue;
}
if (button == self.subItemButtons.lastObject) {
rect.origin.x = CGRectGetWidth(self.subToolBar.bounds) - CGRectGetWidth(rect) - 8.f;
}
button.frame = rect;
rect.origin.x += CGRectGetWidth(rect) + 4.f;
}
}
}
- (void)didSelectItem:(UIButton *)itemButton
{
if (itemButton.tag > 0) {
[self.delegate lmn_toolBar:self didSelectedItemWithTag:itemButton.tag];
}
if (itemButton == self.foldButton || itemButton == self.subToolBarLeftView) {
[self hideSubToolBar:YES];
return;
}
if (itemButton == self.dismissButton) {
[self.delegate lmn_toolBarClose:self];
return;
}
if (itemButton == self.alignmentButton) {
self.alignment = (self.alignment + 1) % 3;
[self.delegate lmn_toolBar:self didChangedTextAlignment:self.alignment];
return;
}
if ([self.subItemButtons containsObject:itemButton]) {
itemButton.selected = !itemButton.selected;
BOOL bold = self.boldButton.selected;
BOOL italic = self.italicButton.selected;
BOOL underline = self.underlineButton.selected;
BOOL strikethrough = self.strikethroughButton.selected;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
attributes[LMFontBoldAttributeName] = @(bold);
attributes[LMFontItalicAttributeName] = @(italic);
attributes[LMFontUnderlineAttributeName] = @(underline);
attributes[LMFontStrikethroughAttributeName] = @(strikethrough);
[self.delegate lmn_toolBar:self didChangedAttributes:attributes];
return;
}
if ([self.itemButtons containsObject:itemButton]) {
if (itemButton == self.fontButton) {
[self showSubToolBar:YES];
}
else if (itemButton == self.titleButton) {
if ([self.mode isEqualToString:LMNLineModeTitle]) {
[self setMode:LMNLineModeSubTitle];
}
else if ([self.mode isEqualToString:LMNLineModeSubTitle]) {
[self setMode:LMNLineModeContent];
}
else {
[self setMode:LMNLineModeTitle];
}
}
else if (itemButton == self.listButton) {
if (self.mode == LMNLineModeBullets) {
[self setMode:LMNLineModeNumbering];
}
else if ([self.mode isEqualToString:LMNLineModeNumbering]) {
[self setMode:LMNLineModeContent];
}
else {
[self setMode:LMNLineModeBullets];
}
}
else if (itemButton == self.checkboxButton) {
if ([self.mode isEqualToString:LMNLineModeCheckbox]) {
[self setMode:LMNLineModeContent];
}
else {
[self setMode:LMNLineModeCheckbox];
}
}
[self.delegate lmn_toolBar:self didChangedMode:self.mode];
return;
}
}
- (void)showSubToolBar:(BOOL)animated
{
self.subToolBar.alpha = 0;
self.subToolBar.hidden = NO;
self.subToolBarLeftView.frame = [self convertRect:self.fontButton.frame fromView:self.toolBar];
self.subToolBarLeftView.hidden = NO;
UIButton *foldButton = self.foldButton;
foldButton.alpha = 0;
foldButton.frame = CGRectOffset(foldButton.frame, -CGRectGetWidth(self.subToolBar.frame), 0);
self.fontButton.hidden = YES;
[UIView animateWithDuration: animated ? 0.35 : 0 animations:^{
self.subToolBar.alpha = 1;
self.toolBar.alpha = 0;
self.toolBar.frame = ({
CGRect rect = self.toolBar.frame;
rect.origin.x += CGRectGetWidth(rect);
rect;
});
self.subToolBarLeftView.frame = ({
CGRect rect = self.subToolBarLeftView.frame;
rect.origin.x = 0;
rect;
});
foldButton.alpha = 1;
foldButton.frame = CGRectOffset(foldButton.frame, CGRectGetWidth(self.subToolBar.frame), 0);
} completion:^(BOOL finished) {
self.toolBar.hidden = YES;
}];
}
- (void)hideSubToolBar:(BOOL)animated
{
UIButton *fontItemButton = self.fontButton;
UIButton *leftButton = self.foldButton;
self.toolBar.alpha = 0;
self.toolBar.hidden = NO;
[UIView animateWithDuration: animated ? 0.35 : 0 animations:^{
self.subToolBar.alpha = 0;
self.toolBar.alpha = 1.f;
self.toolBar.frame = ({
CGRect rect = self.toolBar.frame;
rect.origin.x -= CGRectGetWidth(rect);
rect;
});
self.subToolBarLeftView.frame = [self convertRect:fontItemButton.frame fromView:self.toolBar];
leftButton.alpha = 0;
leftButton.frame = CGRectOffset(leftButton.frame, -CGRectGetWidth(self.subToolBar.frame), 0);
} completion:^(BOOL finished) {
fontItemButton.hidden = NO;
self.subToolBar.hidden = YES;
self.subToolBarLeftView.hidden = YES;
leftButton.frame = CGRectOffset(leftButton.frame, CGRectGetWidth(self.subToolBar.frame), 0);
}];
}
- (void)reloadDataWithTypingAttributes:(NSDictionary *)typingAttributes mode:(LMNLineMode)mode isMultiLine:(BOOL)isMultiLine
{
UIFont *font = typingAttributes[NSFontAttributeName];
BOOL bold = font.bold;
BOOL italic = font.italic;
BOOL underline = (typingAttributes[NSUnderlineStyleAttributeName] != nil);
BOOL strikethrough = (typingAttributes[NSStrikethroughStyleAttributeName] != nil);
if (isMultiLine || ![@[LMNLineModeTitle, LMNLineModeSubTitle, LMNLineModeContent] containsObject:mode]) {
self.alignment = NSTextAlignmentLeft;
self.alignmentButton.enabled = NO;
}
else {
self.alignmentButton.enabled = YES;
NSParagraphStyle *paragraphStyle = typingAttributes[NSParagraphStyleAttributeName];
if (paragraphStyle) {
self.alignment = paragraphStyle.alignment;
}
else {
self.alignment = NSTextAlignmentLeft;
}
}
self.boldButton.selected = bold;
self.italicButton.selected = italic;
self.underlineButton.selected = underline;
self.strikethroughButton.selected = strikethrough;
[self setMode:mode];
}
- (void)setMode:(LMNLineMode)mode
{
if ([_mode isEqualToString:mode]) {
return;
}
_mode = mode;
UIImage *tNormal = [UIImage imageNamed:@"lmn_tool_t"];
UIImage *listNormal = [UIImage imageNamed:@"lmn_list"];
[self.titleButton setImage:tNormal forState:UIControlStateNormal];
[self.listButton setImage:listNormal forState:UIControlStateNormal];
if ([mode isEqualToString:LMNLineModeTitle]) {
UIImage *tTitle = [UIImage imageNamed:@"lmn_tool_title"];
[self.titleButton setImage:tTitle forState:UIControlStateNormal];
}
if ([mode isEqualToString:LMNLineModeSubTitle]) {
UIImage *tSubTitle = [UIImage imageNamed:@"lmn_tool_subtitle"];
[self.titleButton setImage:tSubTitle forState:UIControlStateNormal];
}
if ([mode isEqualToString:LMNLineModeBullets]) {
UIImage *listDot = [UIImage imageNamed:@"lmn_list_dot"];
[self.listButton setImage:listDot forState:UIControlStateNormal];
}
if ([mode isEqualToString:LMNLineModeNumbering]) {
UIImage *listNumber = [UIImage imageNamed:@"lmn_list_number"];
[self.listButton setImage:listNumber forState:UIControlStateNormal];
}
}
- (void)setAlignment:(NSTextAlignment)alignment
{
if (alignment > 2) {
alignment = NSTextAlignmentLeft;
}
if (_alignment == alignment) {
return;
}
_alignment = alignment;
UIImage *image;
switch (alignment) {
case NSTextAlignmentLeft:
image = [UIImage imageNamed:@"lmn_alignment_left"];
break;
case NSTextAlignmentCenter:
image = [UIImage imageNamed:@"lmn_alignment_center"];
break;
case NSTextAlignmentRight:
image = [UIImage imageNamed:@"lmn_alignment_right"];
break;
default:
break;
}
[self.alignmentButton setImage:image forState:UIControlStateNormal];
}
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNWebViewController.h
================================================
//
// LMNWebViewController.h
// LMNote
//
// Created by littleMeaning on 2018/7/12.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface LMNWebViewController : UIViewController
@property (nonatomic, copy) NSString *html;
@end
================================================
FILE: LMNote/LMNote/OtherViews/LMNWebViewController.m
================================================
//
// LMNWebViewController.m
// LMNote
//
// Created by littleMeaning on 2018/7/12.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNWebViewController.h"
@import WebKit;
@interface LMNWebViewController ()
@property (nonatomic, strong) UIWebView *webview;
@end
@implementation LMNWebViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.webview.frame = self.view.bounds;
}
- (UIWebView *)webview
{
if (!_webview) {
_webview = [[UIWebView alloc] init];
[self.view addSubview:_webview];
}
return _webview;
}
- (void)setHtml:(NSString *)html
{
_html = [html copy];
[self.webview loadHTMLString:html baseURL:nil];
}
@end
================================================
FILE: LMNote/LMNote/Store/LMNDraft.h
================================================
//
// LMNDraft.h
// LMNote
//
// Created by littleMeaning on 2018/7/3.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <LMNote/LMNote.h>
@class LMNTextStorage;
@interface LMNDraft : LMNItem
@property (nonatomic, strong) LMNTextStorage *textStorage;
@end
================================================
FILE: LMNote/LMNote/Store/LMNDraft.m
================================================
//
// LMNDraft.m
// LMNote
//
// Created by littleMeaning on 2018/7/3.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNDraft.h"
@implementation LMNDraft
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:self.textStorage forKey:@"textStorage"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.textStorage = [aDecoder decodeObjectForKey:@"textStorage"];
}
return self;
}
@end
================================================
FILE: LMNote/LMNote/Store/LMNFolder.h
================================================
//
// LMNFolder.h
// LMNote
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNItem.h"
@interface LMNFolder : LMNItem <NSCoding>
@property (nonatomic, strong) NSMutableArray<LMNItem *> *contents;
- (void)add:(LMNItem *)item;
- (void)remove:(LMNItem *)item;
@end
================================================
FILE: LMNote/LMNote/Store/LMNFolder.m
================================================
//
// LMNFolder.m
// LMNote
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNFolder.h"
#import "LMNStore.h"
@implementation LMNFolder
- (instancetype)initWithUUID:(NSUUID *)uuid name:(NSString *)name date:(NSDate *)date
{
self = [super initWithUUID:uuid name:name date:date];
if (self) {
self.contents = [NSMutableArray array];
}
return self;
}
- (void)setStore:(LMNStore *)store
{
[super setStore:store];
for (LMNItem *item in self.contents) {
item.store = store;
}
}
- (void)add:(LMNItem *)item
{
item.parent = self;
if ([self.contents containsObject:item]) {
return;
}
[self.contents addObject:item];
[self.store save:item userInfo:nil];
}
- (void)remove:(LMNItem *)item
{
if (![self.contents containsObject:item]) {
return;
}
item.parent = nil;
[self.contents removeObject:item];
[self.store save:item userInfo:nil];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:self.contents forKey:@"contents"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.contents = [aDecoder decodeObjectForKey:@"contents"];
for (LMNItem *item in self.contents) {
[self add:item];
}
if (!self.contents) {
self.contents = [NSMutableArray array];
}
}
return self;
}
@end
================================================
FILE: LMNote/LMNote/Store/LMNItem.h
================================================
//
// LMNItem.h
// LMNote
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <Foundation/Foundation.h>
@class LMNFolder;
@class LMNStore;
@interface LMNItem : NSObject <NSCoding>
@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, weak) LMNStore *store;
@property (nonatomic, weak) LMNFolder *parent;
- (instancetype)initWithUUID:(NSUUID *)uuid name:(NSString *)name date:(NSDate *)date;
- (void)save;
- (void)delete;
@end
================================================
FILE: LMNote/LMNote/Store/LMNItem.m
================================================
//
// LMNItem.m
// LMNote
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNItem.h"
#import "LMNStore.h"
#import "LMNFolder.h"
@interface LMNItem ()
@end
@implementation LMNItem
- (instancetype)initWithUUID:(NSUUID *)uuid name:(NSString *)name date:(NSDate *)date
{
self = [super init];
if (self) {
self.uuid = uuid;
self.name = name;
self.date = date;
}
return self;
}
- (void)setParent:(LMNFolder *)parent
{
_parent = parent;
self.store = parent.store;
}
- (void)save
{
if ([self.parent.contents containsObject:self]) {
[self.store save:self userInfo:nil];
}
else {
[self.parent add:self];
}
}
- (void)delete
{
[self.parent remove:self];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.uuid.UUIDString forKey:@"uuid"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.date forKey:@"date"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.uuid = [[NSUUID alloc] initWithUUIDString:[aDecoder decodeObjectForKey:@"uuid"]];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.date = [aDecoder decodeObjectForKey:@"date"];
}
return self;
}
@end
================================================
FILE: LMNote/LMNote/Store/LMNStore.h
================================================
//
// LMNStore.h
// LMNote
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LMNFolder.h"
#import "LMNItem.h"
extern NSString * const LMNStoreVersion;
extern NSString * const LMNStoreVersionArchiveKey;
extern NSString * const LMNStoreDidChangedNotification;
@interface LMNStore : NSObject
@property (nonatomic, strong) LMNFolder *rootFolder;
+ (instancetype)shared;
- (instancetype)initWithURL:(NSURL *)url;
- (NSURL *)imageDirectory;
- (void)save:(LMNItem *)item userInfo:(NSDictionary *)userInfo;
- (void)reload;
@end
================================================
FILE: LMNote/LMNote/Store/LMNStore.m
================================================
//
// LMNStore.m
// LMNote
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNStore.h"
NSString * const LMNStoreVersion = @"1.0";
NSString * const LMNStoreVersionArchiveKey = @"1.0";
NSString * const LMNStoreDidChangedNotification = @"LMNStoreDidChanged";
@interface LMNStore ()
@property (nonatomic, strong) NSURL *baseURL;
@end
@implementation LMNStore
static NSString * const kStoreLocation = @"archives.dat";
static NSString * const kStoreImageDirectoryName = @"archives_images";
+ (instancetype)shared
{
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
NSURL *documentDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:NULL];
instance = [[self alloc] initWithURL:documentDirectory];
});
return instance;
}
- (instancetype)initWithURL:(NSURL *)url
{
self = [super init];
if (self) {
self.baseURL = url;
[self loadData];
}
return self;
}
- (void)loadData
{
NSURL *fileURL = [self.baseURL URLByAppendingPathComponent:kStoreLocation];
NSData *data = [NSData dataWithContentsOfURL:fileURL];
self.rootFolder = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (!self.rootFolder) {
self.rootFolder = [[LMNFolder alloc] initWithUUID:[NSUUID UUID] name:@"" date:[NSDate date]];
}
self.rootFolder.store = self;
}
- (void)save:(LMNItem *)item userInfo:(NSDictionary *)userInfo
{
NSURL *file = [self.baseURL URLByAppendingPathComponent:kStoreLocation];
if (![NSKeyedArchiver archiveRootObject:self.rootFolder toFile:file.path]) {
// 保存失败
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:LMNStoreDidChangedNotification
object:self
userInfo:userInfo];
}
- (void)reload
{
[self loadData];
}
- (NSURL *)imageDirectory
{
NSURL *url = [self.baseURL URLByAppendingPathComponent:kStoreImageDirectoryName];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:&error];
NSAssert(!error, @"%@", error);
return url;
}
@end
================================================
FILE: LMNote/LMNote/Store/NSTextAttachment+LMNStore.h
================================================
//
// NSTextAttachment+LMNStore.h
// LMNote
//
// Created by littleMeaning on 2018/7/6.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface NSTextAttachment (LMNStore)
@end
================================================
FILE: LMNote/LMNote/Store/NSTextAttachment+LMNStore.m
================================================
//
// NSTextAttachment+LMNStore.m
// LMNote
//
// Created by littleMeaning on 2018/7/6.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "NSTextAttachment+LMNStore.h"
#import <objc/runtime.h>
@implementation NSTextAttachment (LMNStore)
+ (void)load
{
Method oldMethod = class_getInstanceMethod(self, @selector(initWithCoder:));
Method newMethod = class_getInstanceMethod(self, @selector(lmn_initWithCoder:));
method_exchangeImplementations(oldMethod, newMethod);
oldMethod = class_getInstanceMethod(self, @selector(encodeWithCoder:));
newMethod = class_getInstanceMethod(self, @selector(lmn_encodeWithCoder:));
method_exchangeImplementations(oldMethod, newMethod);
}
- (instancetype)lmn_initWithCoder:(NSCoder *)aDecoder
{
NSTextAttachment *instance = [self lmn_initWithCoder:aDecoder];
if (instance) {
instance.bounds = [aDecoder decodeCGRectForKey:@"bounds"];
}
return instance;
}
- (void)lmn_encodeWithCoder:(NSCoder *)aCoder
{
[self lmn_encodeWithCoder:aCoder];
[aCoder encodeCGRect:self.bounds forKey:@"bounds"];
}
@end
================================================
FILE: LMNote/LMNote/Store/UIImage+LMNStore.h
================================================
//
// UIImage+LMNStore.h
// LMNote
//
// Created by littleMeaning on 2018/7/6.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIImage (LMNStore)
@property (nonatomic, strong) NSString *lmn_fileName;
@end
================================================
FILE: LMNote/LMNote/Store/UIImage+LMNStore.m
================================================
//
// UIImage+LMNStore.m
// LMNote
//
// Created by littleMeaning on 2018/7/6.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "UIImage+LMNStore.h"
#import <objc/runtime.h>
@implementation UIImage (LMNStore)
@dynamic lmn_fileName;
- (NSString *)lmn_fileName
{
return objc_getAssociatedObject(self, @selector(lmn_fileName));
}
- (void)setLmn_fileName:(NSString *)lmn_fileName
{
objc_setAssociatedObject(self, @selector(lmn_fileName), lmn_fileName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
================================================
FILE: LMNote/LMNote/TextView/LMNTextStorage+Export.h
================================================
//
// LMNTextStorage+Export.h
// LMNote
//
// Created by littleMeaning on 2018/7/12.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNTextStorage.h"
@interface LMNTextStorage (Export)
- (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion;
@end
================================================
FILE: LMNote/LMNote/TextView/LMNTextStorage+Export.m
================================================
//
// LMNTextStorage+Export.m
// LMNote
//
// Created by littleMeaning on 2018/7/12.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNTextStorage+Export.h"
#import "UIFont+LMNote.h"
#import "LMNLineChain.h"
#import "LMNCheckboxLine.h"
@implementation LMNTextStorage (Export)
// 导出成 HTML 字符串,可以根据具体情况自由添加 css。
- (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSMutableString *html = [NSMutableString string];
LMNLine *line = self.chain.rootLine;
do {
NSRange range = line.range;
if (line.next) {
range.length -= 1;
}
NSAttributedString *attributedText = [self attributedSubstringFromRange:range];
if ([line isKindOfMode:LMNLineModeModeImage]) {
// TODO: 图片部分逻辑未处理。
NSString *imgSrc = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529656898878&di=530af28e54a9418a36e7b27b018f2df1&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201406%2F13%2F20140613210905_SEWfr.jpeg";
[html appendFormat:@"<img src=\"%@\" width=\"100%%\"/>", imgSrc];
}
else {
NSMutableString *lineHTML = [NSMutableString string];
if (attributedText.length > 0) {
NSRange effectiveRange = NSMakeRange(0, 0);
do {
NSDictionary *attributes = [attributedText attributesAtIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange];
NSString *text = [[attributedText string] substringWithRange:effectiveRange];
UIFont *font = attributes[NSFontAttributeName];
BOOL underline = [attributes[NSUnderlineStyleAttributeName] boolValue];
BOOL strikethrough = [attributes[NSStrikethroughStyleAttributeName] integerValue] == 1;
NSString *fragment = [self htmlWithContent:text font:font underline:underline strikethrough:strikethrough];
[lineHTML appendString:fragment];
} while (NSMaxRange(effectiveRange) < attributedText.length);
}
NSString *styleClass = @"content";
if ([line isKindOfMode:LMNLineModeTitle]) {
styleClass = @"title";
}
else if ([line isKindOfMode:LMNLineModeSubTitle]) {
styleClass = @"subtitle";
}
if ([line isKindOfMode:LMNLineModeNumbering]) {
if (![line.prev isKindOfMode:LMNLineModeNumbering]) {
[html appendString:@"<ol>"];
}
[html appendFormat:@"<li class=\"%@\">%@</p>", styleClass, lineHTML];
if (![line.next isKindOfMode:LMNLineModeNumbering]) {
[html appendString:@"</ol>"];
}
}
else if ([line isKindOfMode:LMNLineModeBullets]) {
if (![line.prev isKindOfMode:LMNLineModeBullets]) {
[html appendString:@"<ul>"];
}
[html appendFormat:@"<li class=\"%@\">%@</p>", styleClass, lineHTML];
if (![line.next isKindOfMode:LMNLineModeBullets]) {
[html appendString:@"</ul>"];
}
}
else if ([line isKindOfMode:LMNLineModeCheckbox]) {
NSString *checked = [(LMNCheckboxLine *)line checkboxSelected] ? @"checked " : @"";
[html appendFormat:@"<input class=\"%@\" type=\"checkbox\" %@>%@</p>", styleClass, checked, lineHTML];
}
else {
[html appendFormat:@"<p class=\"%@\">%@</p>", styleClass, lineHTML];
}
}
line = line.next;
} while (line);
dispatch_async(dispatch_get_main_queue(), ^{
[html insertString:@"<!DOCTYPE html><html><style type=\"text/css\">.title{font-size: 24px;};.subtitle {font-size: 20px;};.content{font-size: 17px;}</style><body>" atIndex:0];
[html appendString:@"</body></html>"];
completion(YES, [html copy]);
});
});
}
- (NSString *)htmlWithContent:(NSString *)content
font:(UIFont *)font
underline:(BOOL)underline
strikethrough:(BOOL)strikethrough
{
if (content.length == 0 || font == nil) {
return @"";
}
NSString *html = content;
if (font.bold) {
html = [NSString stringWithFormat:@"<b>%@</b>", html];
}
if (font.italic) {
html = [NSString stringWithFormat:@"<i>%@</i>", html];
}
if (underline) {
html = [NSString stringWithFormat:@"<u>%@</u>", html];
}
if (strikethrough) {
html = [NSString stringWithFormat:@"<s>%@</s>", html];
}
return html;
}
@end
================================================
FILE: LMNote/LMNote/TextView/LMNTextStorage.h
================================================
//
// LMNTextStorage.h
// LMNote
//
// Created by littleMeaning on 2018/3/12.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "LMNLineModes.h"
@class LMNLine;
@class LMNLineChain;
@interface LMNTextStorage : NSTextStorage
@property (nonatomic, strong, readonly) LMNLineChain *chain;
@property (nonatomic, assign, readonly) BOOL inProcessEditing;
- (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range;
- (void)setTextAlignment:(NSTextAlignment)alignment forRange:(NSRange)range;
- (LMNLine *)lineAtLocation:(NSUInteger)location;
- (void)updateNumberingStartWithLine:(LMNLine *)line;
@end
================================================
FILE: LMNote/LMNote/TextView/LMNTextStorage.m
================================================
//
// LMNTextStorage.m
// LMNote
//
// Created by littleMeaning on 2018/3/12.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNTextStorage.h"
#import "LMNLineChain+Numbering.h"
#import "LMNStore.h"
#import "LMNTextLine.h"
#import "LMNNumberingLine.h"
@interface LMNTextStorage ()
@property (nonatomic, strong) LMNLineChain *chain;
@property (nonatomic, assign, readwrite) BOOL inProcessEditing;
@end
@implementation LMNTextStorage
{
NSTextStorage *_imp;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_imp = [aDecoder decodeObjectForKey:@"imp"];
_chain = [aDecoder decodeObjectForKey:@"chain"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_imp forKey:@"imp"];
[aCoder encodeObject:_chain forKey:@"chain"];
}
- (instancetype)init
{
self = [super init];
if (self) {
_imp = [[NSTextStorage alloc] init];
_chain = [[LMNLineChain alloc] init];
}
return self;
}
- (NSString *)string
{
return _imp.string;
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range
{
return [_imp attributesAtIndex:location effectiveRange:range];
}
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
[_imp replaceCharactersInRange:range withString:str];
[self edited:NSTextStorageEditedCharacters range:range changeInLength:(NSInteger)str.length - (NSInteger)range.length];
}
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range
{
[_imp setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
}
- (void)processEditing
{
self.inProcessEditing = YES;
if (self.editedMask & NSTextStorageEditedCharacters &&
!(self.editedRange.length == 0 && self.changeInLength == 0)) {
NSRange editedRange = self.editedRange;
NSRange replacedRange = editedRange;
replacedRange.length -= self.changeInLength;
NSString *originText = self.chain.text;
NSString *replacementText = [self.string substringWithRange:editedRange];
NSString *replacedText = [originText substringWithRange:replacedRange];
LMNLine *forePart = [self.chain lineAtLocation:replacedRange.location]; // 前部分
LMNLine *backPart = [self.chain lineAtLocation:NSMaxRange(replacedRange)]; // 后部分
BOOL shouldUpdateNumbering = NO;
if ([forePart isKindOfClass:[LMNNumberingLine class]] ||
[backPart isKindOfClass:[LMNNumberingLine class]]) {
shouldUpdateNumbering = YES;
}
// 重新整理段落个数
NSInteger replacedCount = [replacedText componentsSeparatedByString:@"\n"].count;
NSInteger replacementCount = [replacementText componentsSeparatedByString:@"\n"].count;
NSInteger changeInCount = MAX(replacementCount, 1) - MAX(replacedCount, 1);
LMNLine *line = forePart;
LMNLine *next = backPart ? backPart.next : forePart.next;
for (NSInteger i = 0; i < changeInCount; i ++) {
LMNLine *newline = nil;
if (i == 0 && [replacementText hasPrefix:@"\n"]) {
newline = [LMNLine lineWithMode:forePart.mode];
[newline inheritFromLine:line]; // 继承上一行的部分属性
}
else {
newline = [LMNLine line];
}
line.next = newline;
line = newline;
}
line.next = next;
[self.chain updateWithText:self.string];
if (shouldUpdateNumbering && changeInCount != 0) {
[self.chain updateNumberings];
}
if (changeInCount < 0 && forePart.attributes) {
NSRange range = self.editedRange;
range.length = NSMaxRange(forePart.range) - range.location;
[self addAttributes:forePart.attributes range:range];
}
}
[super processEditing];
self.inProcessEditing = NO;
}
- (void)fixAttributesInRange:(NSRange)range
{
[super fixAttributesInRange:range];
NSUInteger index = range.location;
while (NSLocationInRange(index, range)) {
LMNLine *line = [self lineAtLocation:index];
if ([self attribute:NSAttachmentAttributeName atIndex:index effectiveRange:NULL]) {
[self removeAttribute:NSParagraphStyleAttributeName range:line.range];
}
else {
NSParagraphStyle *paragraphStyle = line.attributes[NSParagraphStyleAttributeName];
if (paragraphStyle) {
[self addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:line.range];
}
}
index = NSMaxRange(line.range);
};
}
#pragma mark - private
- (void)enumerateParagraphsInRange:(NSRange)range usingBlock:(void (^)(NSRange paragraphRange))block
{
if (!block) {
return;
}
NSRange paragraphRange = NSMakeRange(range.location, 0);
do {
paragraphRange = [self.string paragraphRangeForRange:paragraphRange];
// 执行 block
block(paragraphRange);
paragraphRange.location = NSMaxRange(paragraphRange);
paragraphRange.length = 0;
} while (NSLocationInRange(paragraphRange.location, range));
if ([[self.string substringWithRange:range] isEqualToString:@"\n"]) {
paragraphRange = [self.string paragraphRangeForRange:paragraphRange];
block(paragraphRange);
}
}
- (void)updateLineDisplay:(LMNLine *)line
{
[self setAttributes:line.attributes range:line.range];
NSRange replaceRange = NSMakeRange(line.range.location, 0);
[self replaceCharactersInRange:replaceRange withString:@""]; // 强制刷新行
}
#pragma mark - public
- (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range
{
__block BOOL shouldUpdateNumbering = NO;
[self enumerateParagraphsInRange:range usingBlock:^(NSRange paragraphRange) {
LMNLine *line = [self.chain lineAtLocation:paragraphRange.location];
if ([line isKindOfMode:mode]) {
return;
}
LMNLine *newline = [LMNLine lineWithMode:mode];
[newline insteadOfLine:line];
if ([line isKindOfMode:LMNLineModeNumbering] || [newline isKindOfMode:LMNLineModeNumbering]) {
shouldUpdateNumbering = YES;
}
[self updateLineDisplay:newline];
}];
[self.chain updateWithText:self.string];
if (shouldUpdateNumbering) {
[self.chain updateNumberings];
}
}
- (void)setTextAlignment:(NSTextAlignment)alignment forRange:(NSRange)range
{
LMNLine *line = [self lineAtLocation:range.location];
if ([line isKindOfClass:[LMNTextLine class]]) {
[(LMNTextLine *)line setTextAlignment:alignment];
}
[self updateLineDisplay:line];
}
- (LMNLine *)lineAtLocation:(NSUInteger)location
{
return [self.chain lineAtLocation:location];
}
- (void)updateNumberingStartWithLine:(LMNLine *)line
{
[self.chain updateNumberingStartWithLine:line];
}
@end
================================================
FILE: LMNote/LMNote/TextView/LMNTextView.h
================================================
//
// LMNTextView.h
// LMNote
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "LMNLineModes.h"
@class LMNTextStorage;
@class LMNImageView;
@interface LMNTextView : UITextView
- (instancetype)initWithTextStorage:(LMNTextStorage *)textStorage;
- (LMNLineMode)lineModeForRange:(NSRange)range;
- (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range;
- (void)setAttributesForSelection:(NSDictionary<NSString *,id> *)attributes;
- (void)setTextAlignmentForSelection:(NSTextAlignment)alignment;
- (LMNImageView *)insertImage:(UIImage *)image atIndex:(NSInteger)index;
- (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion;
@end
================================================
FILE: LMNote/LMNote/TextView/LMNTextView.m
================================================
//
// LMNTextView.m
// LMNote
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "LMNTextView.h"
#import "LMNTextStorage.h"
#import "LMNLine.h"
#import "LMNImageView.h"
#import "LMNTextStorage+Export.h"
#import <objc/runtime.h>
#import "LMNSpecialLine.h"
#import "LMNImageLine.h"
#import "LMNTextLine.h"
#define macro_commitUpdating(code); \
BOOL ignore = self.ignoreUpdatingExclusionPaths; \
self.ignoreUpdatingExclusionPaths = YES; \
code \
self.ignoreUpdatingExclusionPaths = ignore; \
if (!ignore) { \
[self updateExclusionPathsIfNeed]; \
} \
@interface LMNTextView () <NSTextStorageDelegate, LMNImageViewDelegate>
@property (nonatomic, weak) LMNImageView *editingImageView;
@property (nonatomic, assign) BOOL needUpdateExclusionPaths;
@property (nonatomic, assign) BOOL ignoreUpdatingExclusionPaths;
@end
@implementation LMNTextView
{
LMNTextStorage *_textStorage;
}
- (instancetype)init
{
return [self initWithTextStorage:nil];
}
- (instancetype)initWithTextStorage:(LMNTextStorage *)textStorage
{
if (!textStorage) {
textStorage = [[LMNTextStorage alloc] init];
}
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
[layoutManager addTextContainer:textContainer];
self = [super initWithFrame:CGRectZero textContainer:textContainer];
if (self) {
_textStorage = textStorage;
self.textContainerInset = UIEdgeInsetsMake(10.f, 15.f, 10.f, 15.f);
self.typingAttributes = [self typingAttributesAtLocation:0];
[self addObservers];
self.needUpdateExclusionPaths = YES;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self updateExclusionPathsIfNeed];
}
- (void)dealloc
{
[self removeObservers];
}
#pragma mark - observer
- (void)addObservers
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(textStorageDidProcessEditing:)
name:NSTextStorageDidProcessEditingNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[_textStorage addObserver:self
forKeyPath:@"inProcessEditing"
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)removeObservers
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_textStorage removeObserver:self forKeyPath:@"inProcessEditing"];
}
#pragma mark - NSTextStorageDidProcessEditingNotification
- (void)textStorageDidProcessEditing:(NSNotification *)notification
{
if (notification.object == _textStorage) {
if (!_textStorage.inProcessEditing) {
[self updateExclusionPathsIfNeed];
}
else {
self.needUpdateExclusionPaths = YES;
}
}
}
- (void)keyboardWillShow:(NSNotification *)notification
{
[self.editingImageView endEditing];
self.editingImageView = nil;
}
#pragma mark - KVC
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if (object == _textStorage && [keyPath isEqualToString:@"inProcessEditing"]) {
BOOL inProcessEditing = [change[NSKeyValueChangeNewKey] boolValue];
if (!inProcessEditing) {
[self updateExclusionPathsIfNeed];
}
}
}
#pragma mark - update exclusionPaths
static CGFloat const kDefaultTextInset = 5.f; // 默认文字会有5.f的缩进
- (void)updateExclusionPaths
{
NSString *text = self.text;
UIEdgeInsets textContainerInset = self.textContainerInset;
NSTextContainer *textContainer = self.textContainer;
LMNTextStorage *textStorage = _textStorage;
__block CGFloat yOffset = 0;
__block NSRange range = NSMakeRange(0, 0);
CGFloat limitWidth = textContainer.size.width - kDefaultTextInset * 2;
NSMutableArray *exclusionPaths = [NSMutableArray array];
[text enumerateLinesUsingBlock:^(NSString *textLine, BOOL *stop) {
range.length = textLine.length;
LMNLine *line = [textStorage lineAtLocation:range.location];
NSAttributedString *attributedText = [textStorage attributedSubstringFromRange:range];
CGFloat lineHeight = 0;
UIFont *font = line.attributes[NSFontAttributeName];
NSParagraphStyle *paragraphStyle = line.attributes[NSParagraphStyleAttributeName];
if (range.length == 0) {
lineHeight = font.lineHeight;
}
else {
CGSize limitSize = CGSizeZero;
if ([line isKindOfClass:[LMNSpecialLine class]]) {
limitSize.width = limitWidth - ((LMNSpecialLine *)line).intrinsicLeftSize.width;
}
else {
limitSize.width = limitWidth;
}
lineHeight = [attributedText boundingRectWithSize:limitSize
options:NSStringDrawingUsesLineFragmentOrigin
context:NULL].size.height;
if (font) {
// 存在粗体、下划线时,考虑由于 lineSpacing 对计算结果的影响
id tmp = [attributedText mutableCopy];
[tmp addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, attributedText.length)];
[tmp removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, attributedText.length)];
[tmp removeAttribute:NSStrikethroughStyleAttributeName range:NSMakeRange(0, attributedText.length)];
attributedText = tmp;
CGFloat estimatedLineHeight = [tmp boundingRectWithSize:limitSize options:NSStringDrawingUsesLineFragmentOrigin context:NULL].size.height;
CGFloat spacing = paragraphStyle.lineSpacing + paragraphStyle.paragraphSpacing;
if (lineHeight - estimatedLineHeight <= spacing) {
lineHeight = estimatedLineHeight;
}
}
}
if (paragraphStyle && ![line isKindOfClass:[LMNImageLine class]]) {
lineHeight += paragraphStyle.lineSpacing;
lineHeight += paragraphStyle.paragraphSpacing;
lineHeight += paragraphStyle.paragraphSpacingBefore;
if (range.location == 0) {
// 首行没有段前间距
yOffset -= paragraphStyle.paragraphSpacingBefore;
}
}
if ([line isKindOfClass:[LMNTextLine class]]) {
yOffset += lineHeight;
range.location = NSMaxRange(range) + 1;
return;
}
// 给行样式添加 exclusionPath
CGRect rect = CGRectZero;
rect.origin.y = ceilf(yOffset);
rect.size.height = floorf(yOffset + lineHeight) - rect.origin.y;
if ([line isKindOfClass:[LMNSpecialLine class]]) {
LMNSpecialLine *specialLine = (LMNSpecialLine *)line;
rect.size.width = [specialLine intrinsicLeftSize].width;
// 1.f 是为小数精度做微调。
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectInset(rect, 0, 1.f)];
[exclusionPaths addObject:path];
if (!specialLine.leftView) {
[specialLine loadLeftView];
}
if (!specialLine.leftView.superview) {
[self addSubview:specialLine.leftView];
}
rect.origin.x = textContainerInset.left;
rect.origin.y += textContainerInset.top;
rect.size = [specialLine intrinsicLeftSize];
if (rect.size.height == 0) {
rect.size.height = lineHeight;
}
specialLine.leftView.frame = rect;
}
else if ([line isKindOfClass:[LMNImageLine class]]) {
// 图片
LMNImageLine *imageLine = (LMNImageLine *)line;
LMNImageView *imageView = imageLine.bindingImageView;
CGFloat width = CGRectGetWidth(self.frame);
UIEdgeInsets textContainerInset = self.textContainerInset;
width -= (textContainerInset.left + textContainerInset.right + kDefaultTextInset * 2);
rect.size = [LMNImageView sizeThatFit:imageLine.image limitWidth:width];
if (imageView == nil && imageLine.image != nil) {
imageView = [[LMNImageView alloc] initWithImage:imageLine.image];
imageView.delegate = self;
[self addSubview:imageView];
[imageLine bindImageView:imageView];
}
if (imageView) {
rect.origin.x = kDefaultTextInset + textContainerInset.left;
rect.origin.y += textContainerInset.top;
imageView.frame = rect;
}
}
yOffset += lineHeight;
range.location = NSMaxRange(range) + 1;
}];
// 派发到下次任务中,不然会 Crash
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollEnabled = NO;
self.textContainer.exclusionPaths = exclusionPaths;
self.scrollEnabled = YES;
});
}
- (void)updateExclusionPathsIfNeed
{
if (CGRectGetWidth(self.bounds) == 0) {
return;
}
if (self.needUpdateExclusionPaths && !self.ignoreUpdatingExclusionPaths) {
[self updateExclusionPaths];
self.needUpdateExclusionPaths = NO;
}
}
#pragma mark - extraLine
- (BOOL)isSelectedExtraLine
{
return ([self.text hasSuffix:@"\n"] && NSMaxRange(self.selectedRange) == self.text.length);
}
// 通过 [NSLayoutManager -drawBackgroundForGlyphRange:atPoint:] 来给行绘制项目符号,但对于没有内容最后一行(extraLine)则不会触发绘制方法,这里添加特殊处理,给 extraLine 添加一个 "\n" 以触发绘制方法。
- (void)appendingLineBreakForExtraLine
{
NSRange paragraphRange = [self.text paragraphRangeForRange:self.selectedRange];
NSString *endingText = [self.text substringWithRange:paragraphRange];
if (![endingText containsString:@"\n"]) {
// 选中行为最后一行
NSRange range = NSMakeRange(self.text.length, 0);
LMNLine *lastLine = [_textStorage lineAtLocation:range.location];
if ([lastLine isKindOfClass:[LMNSpecialLine class]]) {
[_textStorage replaceCharactersInRange:range withAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:lastLine.attributes]];
[[LMNLine line] insteadOfLine:lastLine.next];
}
}
}
#pragma mark - override
- (BOOL)shouldDeleteBackward
{
NSRange selectedRange = self.selectedRange;
if (selectedRange.length == 0 && selectedRange.location > 0) {
LMNLine *line = [_textStorage lineAtLocation:selectedRange.location - 1];
if (NSMaxRange(line.range) == selectedRange.location &&
[line isKindOfClass:[LMNImageLine class]]) {
[((LMNImageLine *)line).bindingImageView beginEditing];
return NO;
}
}
return YES;
}
- (void)deleteBackward
{
if (![self shouldDeleteBackward]) {
return;
}
if (NSEqualRanges(self.selectedRange, NSMakeRange(0, 0))) {
LMNLine *line = [_textStorage lineAtLocation:0];
if ([line isKindOfClass:[LMNSpecialLine class]]) {
[_textStorage setLineMode:LMNLineModeContent forRange:NSMakeRange(0, 0)];
return;
}
}
macro_commitUpdating({
[super deleteBackward];
[self appendingLineBreakForExtraLine];
});
}
- (void)insertText:(NSString *)text
{
#pragma warning - 粘贴不走该逻辑
if ([text isEqualToString:@"\n"] && self.selectedRange.length == 0) {
LMNLine *line = [_textStorage lineAtLocation:self.selectedRange.location];
if ([line isKindOfClass:[LMNSpecialLine class]]) {
NSString *prevStr = self.selectedRange.location == 0 ? @"\n" : [self.text substringWithRange:NSMakeRange(self.selectedRange.location - 1, 1)];
NSString *nextStr = [self.text substringWithRange:NSMakeRange(self.selectedRange.location, 1)];
if ([prevStr isEqualToString:@"\n"] && [nextStr isEqualToString:@"\n"]) {
[self setLineMode:LMNLineModeContent forRange:self.selectedRange];
return;
}
}
}
macro_commitUpdating({
[super insertText:text];
[self appendingLineBreakForExtraLine];
});
}
#pragma mark - private
- (NSDictionary *)typingAttributesAtLocation:(NSUInteger)location
{
return [_textStorage lineAtLocation:location].attributes;
}
#pragma mark - public method
- (LMNLineMode)lineModeForRange:(NSRange)range
{
LMNLine *line = [_textStorage lineAtLocation:range.location];
return line.mode;
}
- (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range
{
macro_commitUpdating({
[_textStorage setLineMode:mode forRange:range];
[self appendingLineBreakForExtraLine];
});
self.typingAttributes = [self typingAttributesAtLocation:range.location];
}
- (void)setAttributesForSelection:(NSDictionary<NSString *,id> *)attributes
{
[_textStorage setAttributes:attributes range:self.selectedRange];
}
- (void)setTextAlignmentForSelection:(NSTextAlignment)alignment
{
LMNTextStorage *textStorage = (LMNTextStorage *)_textStorage;
[textStorage setTextAlignment:alignment forRange:self.selectedRange];
}
- (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion
{
[_textStorage exportHTML:completion];
}
#pragma mark - image
- (LMNImageView *)insertImage:(UIImage *)image atIndex:(NSInteger)index
{
if (index >= self.text.length) {
index = self.text.length;
}
CGFloat width = CGRectGetWidth(self.frame);
UIEdgeInsets textContainerInset = self.textContainerInset;
width -= (textContainerInset.left + textContainerInset.right + kDefaultTextInset * 2);
CGSize size = [LMNImageView sizeThatFit:image limitWidth:width];
LMNImageView *imageView = [[LMNImageView alloc] initWithImage:image];
imageView.delegate = self;
[self addSubview:imageView];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
CGRect bounds = CGRectZero;
bounds.size = size;
attachment.image = [UIImage new];
attachment.bounds = bounds;
NSAttributedString *imgStr = [NSAttributedString attributedStringWithAttachment:attachment];
LMNLine *lineAtIndex = [_textStorage lineAtLocation:index];
NSMutableAttributedString *replacementString = [[NSMutableAttributedString alloc] init];
[replacementString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:lineAtIndex.attributes]];
BOOL inCurrentLine = NO;
NSUInteger insertLocation = NSMaxRange(lineAtIndex.range) - 1;
if (lineAtIndex.range.location == index) {
// 如果光标位置是行首,则图片在该行之前
inCurrentLine = YES;
insertLocation = lineAtIndex.range.location;
}
else if (lineAtIndex.next == nil) {
// 最后一行,则在图片之后新增一行。
[replacementString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:[LMNLine line].attributes]];
insertLocation ++;
}
[replacementString insertAttributedString:imgStr atIndex:inCurrentLine ? 0 : 1];
macro_commitUpdating({
[_textStorage replaceCharactersInRange:NSMakeRange(insertLocation, 0) withAttributedString:replacementString];
LMNLine *line = [_textStorage lineAtLocation:index];
if (!inCurrentLine) {
line = line.next;
}
if (line.next.range.length == 0) {
LMNLine *newline = [LMNLine lineWithMode:LMNLineModeContent];
[newline insteadOfLine:line.next];
}
LMNLine *newline = [LMNLine lineWithMode:LMNLineModeModeImage];
[newline insteadOfLine:line];
[(LMNImageLine *)newline bindImageView:imageView];
[_textStorage updateNumberingStartWithLine:newline.next];
});
return imageView;
}
#pragma mark <LMNImageViewDelegate>
- (void)lmn_imageViewBeginEditing:(LMNImageView *)imageView
{
[self.editingImageView endEditing];
self.editingImageView = imageView;
[self resignFirstResponder];
}
- (void)lmn_imageViewEndEditing:(LMNImageView *)imageView
{
self.editingImageView = nil;
}
- (void)lmn_imageViewDelete:(LMNImageView *)imageView
{
LMNLine *line = imageView.owner;
LMNLine *nextline = line.next;
[imageView removeFromSuperview];
[imageView unbindFromOwner];
macro_commitUpdating({
[_textStorage replaceCharactersInRange:line.range withString:@""];
[nextline insteadOfLine:line];
[_textStorage updateNumberingStartWithLine:nextline];
});
}
@end
================================================
FILE: LMNote/LMNote/TextView/UIFont+LMNote.h
================================================
//
// UIFont+LMNote.h
// SimpleWord
//
// Created by littleMeaning on 16/6/30.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIFont (LMNote)
@property (nonatomic, readonly) BOOL bold;
@property (nonatomic, readonly) BOOL italic;
@property (nonatomic, readonly) float fontSize;
+ (instancetype)fontWithFontSize:(float)fontSize bold:(BOOL)bold italic:(BOOL)italic;
@end
================================================
FILE: LMNote/LMNote/TextView/UIFont+LMNote.m
================================================
//
// UIFont+LMNote.m
// SimpleWord
//
// Created by littleMeaning on 16/6/30.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import "UIFont+LMNote.h"
@implementation UIFont (LMNote)
+ (instancetype)fontWithFontSize:(float)fontSize bold:(BOOL)bold italic:(BOOL)italic {
UIFont *font = [UIFont fontWithName:@"PingFang SC" size:fontSize];
UIFontDescriptor *existingDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits traits = existingDescriptor.symbolicTraits;
if (bold) {
traits |= UIFontDescriptorTraitBold;
}
if (italic) {
traits |= UIFontDescriptorTraitItalic;
}
UIFontDescriptor *descriptor = [existingDescriptor fontDescriptorWithSymbolicTraits:traits];
return [UIFont fontWithDescriptor:descriptor size:fontSize];
}
- (BOOL)bold {
return self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold;
}
- (BOOL)italic {
return self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic;
}
- (float)fontSize {
return [self.fontDescriptor.fontAttributes[@"NSFontSizeAttribute"] floatValue];
}
@end
================================================
FILE: LMNote/LMNote.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
7401227B2005E4CF0072C387 /* LMNote.h in Headers */ = {isa = PBXBuildFile; fileRef = 740122792005E4CF0072C387 /* LMNote.h */; settings = {ATTRIBUTES = (Public, ); }; };
740122902005E6C80072C387 /* LMNTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 7401228E2005E6C80072C387 /* LMNTextView.h */; settings = {ATTRIBUTES = (Public, ); }; };
740122912005E6C80072C387 /* LMNTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7401228F2005E6C80072C387 /* LMNTextView.m */; };
7407767520E9FE400022F99E /* LMNStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 7407767320E9FE400022F99E /* LMNStore.h */; settings = {ATTRIBUTES = (Public, ); }; };
7407767620E9FE400022F99E /* LMNStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 7407767420E9FE400022F99E /* LMNStore.m */; };
7407768120EA153F0022F99E /* LMNItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 7407767F20EA153F0022F99E /* LMNItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
7407768220EA153F0022F99E /* LMNItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 7407768020EA153F0022F99E /* LMNItem.m */; };
7407768520EA15DA0022F99E /* LMNFolder.h in Headers */ = {isa = PBXBuildFile; fileRef = 7407768320EA15DA0022F99E /* LMNFolder.h */; settings = {ATTRIBUTES = (Public, ); }; };
7407768620EA15DA0022F99E /* LMNFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = 7407768420EA15DA0022F99E /* LMNFolder.m */; };
7407769320EB8CF30022F99E /* LMNDraft.h in Headers */ = {isa = PBXBuildFile; fileRef = 7407769120EB8CF30022F99E /* LMNDraft.h */; settings = {ATTRIBUTES = (Public, ); }; };
7407769420EB8CF30022F99E /* LMNDraft.m in Sources */ = {isa = PBXBuildFile; fileRef = 7407769220EB8CF30022F99E /* LMNDraft.m */; };
740776A420EF39D40022F99E /* NSTextAttachment+LMNStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 740776A220EF39D40022F99E /* NSTextAttachment+LMNStore.h */; };
740776A520EF39D40022F99E /* NSTextAttachment+LMNStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 740776A320EF39D40022F99E /* NSTextAttachment+LMNStore.m */; };
740F632820561E23001A6FA9 /* LMNTextStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 740F632620561E23001A6FA9 /* LMNTextStorage.h */; };
740F632920561E23001A6FA9 /* LMNTextStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 740F632720561E23001A6FA9 /* LMNTextStorage.m */; };
7417CADF20EF46AD00ED84AC /* UIImage+LMNStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CADD20EF46AD00ED84AC /* UIImage+LMNStore.h */; };
7417CAE020EF46AD00ED84AC /* UIImage+LMNStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CADE20EF46AD00ED84AC /* UIImage+LMNStore.m */; };
7417CAE720F301E800ED84AC /* LMNLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CAE520F301E800ED84AC /* LMNLine.h */; };
7417CAE820F301E800ED84AC /* LMNLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CAE620F301E800ED84AC /* LMNLine.m */; };
7417CAEB20F3030D00ED84AC /* LMNCheckboxLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CAE920F3030D00ED84AC /* LMNCheckboxLine.h */; };
7417CAEC20F3030D00ED84AC /* LMNCheckboxLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CAEA20F3030D00ED84AC /* LMNCheckboxLine.m */; };
7417CAF320F305EE00ED84AC /* LMNTextLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CAF120F305EE00ED84AC /* LMNTextLine.h */; };
7417CAF420F305EE00ED84AC /* LMNTextLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CAF220F305EE00ED84AC /* LMNTextLine.m */; };
7417CAF720F3060000ED84AC /* LMNSpecialLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CAF520F3060000ED84AC /* LMNSpecialLine.h */; };
7417CAF820F3060000ED84AC /* LMNSpecialLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CAF620F3060000ED84AC /* LMNSpecialLine.m */; };
7417CAFB20F3066000ED84AC /* LMNNumberingLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CAF920F3066000ED84AC /* LMNNumberingLine.h */; };
7417CAFC20F3066000ED84AC /* LMNNumberingLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CAFA20F3066000ED84AC /* LMNNumberingLine.m */; };
7417CAFF20F306D000ED84AC /* LMNBulletsLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CAFD20F306D000ED84AC /* LMNBulletsLine.h */; };
7417CB0020F306D000ED84AC /* LMNBulletsLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CAFE20F306D000ED84AC /* LMNBulletsLine.m */; };
7417CB0320F306FF00ED84AC /* LMNImageLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CB0120F306FF00ED84AC /* LMNImageLine.h */; };
7417CB0420F306FF00ED84AC /* LMNImageLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CB0220F306FF00ED84AC /* LMNImageLine.m */; };
7417CB0720F30DAF00ED84AC /* LMNLineChain+Numbering.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CB0520F30DAF00ED84AC /* LMNLineChain+Numbering.h */; };
7417CB0820F30DAF00ED84AC /* LMNLineChain+Numbering.m in Sources */ = {isa = PBXBuildFile; fileRef = 7417CB0620F30DAF00ED84AC /* LMNLineChain+Numbering.m */; };
7417CB1320F49EFE00ED84AC /* LMNLineModes.h in Headers */ = {isa = PBXBuildFile; fileRef = 7417CB1220F49E7900ED84AC /* LMNLineModes.h */; settings = {ATTRIBUTES = (Public, ); }; };
7450D18F20858C03005BD352 /* LMNToolBar.h in Headers */ = {isa = PBXBuildFile; fileRef = 7450D18D20858C03005BD352 /* LMNToolBar.h */; };
7450D19020858C03005BD352 /* LMNToolBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 7450D18E20858C03005BD352 /* LMNToolBar.m */; };
7450D195208596FC005BD352 /* LMNoteViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7450D193208596FB005BD352 /* LMNoteViewController.m */; };
7450D196208596FC005BD352 /* LMNoteViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7450D194208596FB005BD352 /* LMNoteViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
7450D19920883544005BD352 /* LMNImageInputViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7450D19720883544005BD352 /* LMNImageInputViewController.h */; };
7450D19A20883544005BD352 /* LMNImageInputViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7450D19820883544005BD352 /* LMNImageInputViewController.m */; };
7450D19D20883E83005BD352 /* LMNPhotoCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7450D19B20883E83005BD352 /* LMNPhotoCollectionCell.m */; };
7450D19E20883E84005BD352 /* LMNPhotoCollectionCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 7450D19C20883E83005BD352 /* LMNPhotoCollectionCell.h */; };
747AAC1E208F03D5008FDB52 /* LMNImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 747AAC1C208F03D5008FDB52 /* LMNImageView.h */; };
747AAC1F208F03D5008FDB52 /* LMNImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 747AAC1D208F03D5008FDB52 /* LMNImageView.m */; };
74BD77112073214B00685100 /* UIFont+LMNote.m in Sources */ = {isa = PBXBuildFile; fileRef = 74BD770F2073214A00685100 /* UIFont+LMNote.m */; };
74BD77122073214B00685100 /* UIFont+LMNote.h in Headers */ = {isa = PBXBuildFile; fileRef = 74BD77102073214B00685100 /* UIFont+LMNote.h */; };
74C94AC220F72A5A00CC3C27 /* LMNTextStorage+Export.h in Headers */ = {isa = PBXBuildFile; fileRef = 74C94AC020F72A5A00CC3C27 /* LMNTextStorage+Export.h */; };
74C94AC320F72A5A00CC3C27 /* LMNTextStorage+Export.m in Sources */ = {isa = PBXBuildFile; fileRef = 74C94AC120F72A5A00CC3C27 /* LMNTextStorage+Export.m */; };
74C94AC620F731DE00CC3C27 /* LMNWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 74C94AC420F731DE00CC3C27 /* LMNWebViewController.h */; };
74C94AC720F731DE00CC3C27 /* LMNWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 74C94AC520F731DE00CC3C27 /* LMNWebViewController.m */; };
74D4F7B7206DCB71007CFE8D /* LMNLineChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 74D4F7B5206DCB70007CFE8D /* LMNLineChain.m */; };
74D4F7B8206DCB71007CFE8D /* LMNLineChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 74D4F7B6206DCB70007CFE8D /* LMNLineChain.h */; };
74E9256420D8D71D00383146 /* Feature in Resources */ = {isa = PBXBuildFile; fileRef = 74E9256320D8D71D00383146 /* Feature */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
740122762005E4CF0072C387 /* LMNote.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LMNote.framework; sourceTree = BUILT_PRODUCTS_DIR; };
740122792005E4CF0072C387 /* LMNote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNote.h; sourceTree = "<group>"; };
7401228E2005E6C80072C387 /* LMNTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNTextView.h; sourceTree = "<group>"; };
7401228F2005E6C80072C387 /* LMNTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNTextView.m; sourceTree = "<group>"; };
7407767320E9FE400022F99E /* LMNStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNStore.h; sourceTree = "<group>"; };
7407767420E9FE400022F99E /* LMNStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNStore.m; sourceTree = "<group>"; };
7407767F20EA153F0022F99E /* LMNItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNItem.h; sourceTree = "<group>"; };
7407768020EA153F0022F99E /* LMNItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNItem.m; sourceTree = "<group>"; };
7407768320EA15DA0022F99E /* LMNFolder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNFolder.h; sourceTree = "<group>"; };
7407768420EA15DA0022F99E /* LMNFolder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNFolder.m; sourceTree = "<group>"; };
7407769120EB8CF30022F99E /* LMNDraft.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNDraft.h; sourceTree = "<group>"; };
7407769220EB8CF30022F99E /* LMNDraft.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNDraft.m; sourceTree = "<group>"; };
740776A220EF39D40022F99E /* NSTextAttachment+LMNStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSTextAttachment+LMNStore.h"; sourceTree = "<group>"; };
740776A320EF39D40022F99E /* NSTextAttachment+LMNStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSTextAttachment+LMNStore.m"; sourceTree = "<group>"; };
740F632620561E23001A6FA9 /* LMNTextStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNTextStorage.h; sourceTree = "<group>"; };
740F632720561E23001A6FA9 /* LMNTextStorage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNTextStorage.m; sourceTree = "<group>"; };
7417CADD20EF46AD00ED84AC /* UIImage+LMNStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImage+LMNStore.h"; sourceTree = "<group>"; };
7417CADE20EF46AD00ED84AC /* UIImage+LMNStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIImage+LMNStore.m"; sourceTree = "<group>"; };
7417CAE520F301E800ED84AC /* LMNLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMNLine.h; sourceTree = "<group>"; };
7417CAE620F301E800ED84AC /* LMNLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMNLine.m; sourceTree = "<group>"; };
7417CAE920F3030D00ED84AC /* LMNCheckboxLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNCheckboxLine.h; sourceTree = "<group>"; };
7417CAEA20F3030D00ED84AC /* LMNCheckboxLine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNCheckboxLine.m; sourceTree = "<group>"; };
7417CAF120F305EE00ED84AC /* LMNTextLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNTextLine.h; sourceTree = "<group>"; };
7417CAF220F305EE00ED84AC /* LMNTextLine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNTextLine.m; sourceTree = "<group>"; };
7417CAF520F3060000ED84AC /* LMNSpecialLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNSpecialLine.h; sourceTree = "<group>"; };
7417CAF620F3060000ED84AC /* LMNSpecialLine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNSpecialLine.m; sourceTree = "<group>"; };
7417CAF920F3066000ED84AC /* LMNNumberingLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNNumberingLine.h; sourceTree = "<group>"; };
7417CAFA20F3066000ED84AC /* LMNNumberingLine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNNumberingLine.m; sourceTree = "<group>"; };
7417CAFD20F306D000ED84AC /* LMNBulletsLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNBulletsLine.h; sourceTree = "<group>"; };
7417CAFE20F306D000ED84AC /* LMNBulletsLine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNBulletsLine.m; sourceTree = "<group>"; };
7417CB0120F306FF00ED84AC /* LMNImageLine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNImageLine.h; sourceTree = "<group>"; };
7417CB0220F306FF00ED84AC /* LMNImageLine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNImageLine.m; sourceTree = "<group>"; };
7417CB0520F30DAF00ED84AC /* LMNLineChain+Numbering.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LMNLineChain+Numbering.h"; sourceTree = "<group>"; };
7417CB0620F30DAF00ED84AC /* LMNLineChain+Numbering.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "LMNLineChain+Numbering.m"; sourceTree = "<group>"; };
7417CB1220F49E7900ED84AC /* LMNLineModes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNLineModes.h; sourceTree = "<group>"; };
7450D18D20858C03005BD352 /* LMNToolBar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNToolBar.h; sourceTree = "<group>"; };
7450D18E20858C03005BD352 /* LMNToolBar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNToolBar.m; sourceTree = "<group>"; };
7450D193208596FB005BD352 /* LMNoteViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMNoteViewController.m; sourceTree = "<group>"; };
7450D194208596FB005BD352 /* LMNoteViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMNoteViewController.h; sourceTree = "<group>"; };
7450D19720883544005BD352 /* LMNImageInputViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNImageInputViewController.h; sourceTree = "<group>"; };
7450D19820883544005BD352 /* LMNImageInputViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNImageInputViewController.m; sourceTree = "<group>"; };
7450D19B20883E83005BD352 /* LMNPhotoCollectionCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMNPhotoCollectionCell.m; sourceTree = "<group>"; };
7450D19C20883E83005BD352 /* LMNPhotoCollectionCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMNPhotoCollectionCell.h; sourceTree = "<group>"; };
747AAC1C208F03D5008FDB52 /* LMNImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNImageView.h; sourceTree = "<group>"; };
747AAC1D208F03D5008FDB52 /* LMNImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNImageView.m; sourceTree = "<group>"; };
74BD770F2073214A00685100 /* UIFont+LMNote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+LMNote.m"; sourceTree = "<group>"; };
74BD77102073214B00685100 /* UIFont+LMNote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+LMNote.h"; sourceTree = "<group>"; };
74C94AC020F72A5A00CC3C27 /* LMNTextStorage+Export.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LMNTextStorage+Export.h"; sourceTree = "<group>"; };
74C94AC120F72A5A00CC3C27 /* LMNTextStorage+Export.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "LMNTextStorage+Export.m"; sourceTree = "<group>"; };
74C94AC420F731DE00CC3C27 /* LMNWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMNWebViewController.h; sourceTree = "<group>"; };
74C94AC520F731DE00CC3C27 /* LMNWebViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMNWebViewController.m; sourceTree = "<group>"; };
74D4F7B5206DCB70007CFE8D /* LMNLineChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMNLineChain.m; sourceTree = "<group>"; };
74D4F7B6206DCB70007CFE8D /* LMNLineChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMNLineChain.h; sourceTree = "<group>"; };
74E9256320D8D71D00383146 /* Feature */ = {isa = PBXFileReference; lastKnownFileType = text; path = Feature; sourceTree = "<group>"; };
74F75FD320063CFF008CD64B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
740122722005E4CF0072C387 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7401226C2005E4CF0072C387 = {
isa = PBXGroup;
children = (
740122782005E4CF0072C387 /* LMNote */,
74F75FD220063CFF008CD64B /* Supporting Files */,
740122772005E4CF0072C387 /* Products */,
);
sourceTree = "<group>";
};
740122772005E4CF0072C387 /* Products */ = {
isa = PBXGroup;
children = (
740122762005E4CF0072C387 /* LMNote.framework */,
);
name = Products;
sourceTree = "<group>";
};
740122782005E4CF0072C387 /* LMNote */ = {
isa = PBXGroup;
children = (
74E9256320D8D71D00383146 /* Feature */,
740122792005E4CF0072C387 /* LMNote.h */,
7450D194208596FB005BD352 /* LMNoteViewController.h */,
7450D193208596FB005BD352 /* LMNoteViewController.m */,
7407769620EB8F4C0022F99E /* TextView */,
7417CB0F20F44D5400ED84AC /* Lines */,
7417CB1120F49D4C00ED84AC /* Store */,
7407769720EB8F6B0022F99E /* OtherViews */,
);
path = LMNote;
sourceTree = "<group>";
};
7407769620EB8F4C0022F99E /* TextView */ = {
isa = PBXGroup;
children = (
74BD77102073214B00685100 /* UIFont+LMNote.h */,
74BD770F2073214A00685100 /* UIFont+LMNote.m */,
740F632620561E23001A6FA9 /* LMNTextStorage.h */,
740F632720561E23001A6FA9 /* LMNTextStorage.m */,
7401228E2005E6C80072C387 /* LMNTextView.h */,
7401228F2005E6C80072C387 /* LMNTextView.m */,
74C94AC020F72A5A00CC3C27 /* LMNTextStorage+Export.h */,
74C94AC120F72A5A00CC3C27 /* LMNTextStorage+Export.m */,
);
path = TextView;
sourceTree = "<group>";
};
7407769720EB8F6B0022F99E /* OtherViews */ = {
isa = PBXGroup;
children = (
7450D18D20858C03005BD352 /* LMNToolBar.h */,
7450D18E20858C03005BD352 /* LMNToolBar.m */,
7450D19720883544005BD352 /* LMNImageInputViewController.h */,
7450D19820883544005BD352 /* LMNImageInputViewController.m */,
747AAC1C208F03D5008FDB52 /* LMNImageView.h */,
747AAC1D208F03D5008FDB52 /* LMNImageView.m */,
7450D19C20883E83005BD352 /* LMNPhotoCollectionCell.h */,
7450D19B20883E83005BD352 /* LMNPhotoCollectionCell.m */,
74C94AC420F731DE00CC3C27 /* LMNWebViewController.h */,
74C94AC520F731DE00CC3C27 /* LMNWebViewController.m */,
);
path = OtherViews;
sourceTree = "<group>";
};
7417CB0F20F44D5400ED84AC /* Lines */ = {
isa = PBXGroup;
children = (
7417CB1220F49E7900ED84AC /* LMNLineModes.h */,
7417CAE520F301E800ED84AC /* LMNLine.h */,
7417CAE620F301E800ED84AC /* LMNLine.m */,
7417CAF120F305EE00ED84AC /* LMNTextLine.h */,
7417CAF220F305EE00ED84AC /* LMNTextLine.m */,
7417CAF520F3060000ED84AC /* LMNSpecialLine.h */,
7417CAF620F3060000ED84AC /* LMNSpecialLine.m */,
7417CAFD20F306D000ED84AC /* LMNBulletsLine.h */,
7417CAFE20F306D000ED84AC /* LMNBulletsLine.m */,
7417CAE920F3030D00ED84AC /* LMNCheckboxLine.h */,
7417CAEA20F3030D00ED84AC /* LMNCheckboxLine.m */,
7417CAF920F3066000ED84AC /* LMNNumberingLine.h */,
7417CAFA20F3066000ED84AC /* LMNNumberingLine.m */,
7417CB0120F306FF00ED84AC /* LMNImageLine.h */,
7417CB0220F306FF00ED84AC /* LMNImageLine.m */,
74D4F7B6206DCB70007CFE8D /* LMNLineChain.h */,
74D4F7B5206DCB70007CFE8D /* LMNLineChain.m */,
7417CB0520F30DAF00ED84AC /* LMNLineChain+Numbering.h */,
7417CB0620F30DAF00ED84AC /* LMNLineChain+Numbering.m */,
);
path = Lines;
sourceTree = "<group>";
};
7417CB1120F49D4C00ED84AC /* Store */ = {
isa = PBXGroup;
children = (
7407767F20EA153F0022F99E /* LMNItem.h */,
7407768020EA153F0022F99E /* LMNItem.m */,
7407768320EA15DA0022F99E /* LMNFolder.h */,
7407768420EA15DA0022F99E /* LMNFolder.m */,
7407769120EB8CF30022F99E /* LMNDraft.h */,
7407769220EB8CF30022F99E /* LMNDraft.m */,
7407767320E9FE400022F99E /* LMNStore.h */,
7407767420E9FE400022F99E /* LMNStore.m */,
740776A220EF39D40022F99E /* NSTextAttachment+LMNStore.h */,
740776A320EF39D40022F99E /* NSTextAttachment+LMNStore.m */,
7417CADD20EF46AD00ED84AC /* UIImage+LMNStore.h */,
7417CADE20EF46AD00ED84AC /* UIImage+LMNStore.m */,
);
path = Store;
sourceTree = "<group>";
};
74F75FD220063CFF008CD64B /* Supporting Files */ = {
isa = PBXGroup;
children = (
74F75FD320063CFF008CD64B /* Info.plist */,
);
path = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
740122732005E4CF0072C387 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7417CB1320F49EFE00ED84AC /* LMNLineModes.h in Headers */,
7450D196208596FC005BD352 /* LMNoteViewController.h in Headers */,
7417CB0720F30DAF00ED84AC /* LMNLineChain+Numbering.h in Headers */,
7417CAF720F3060000ED84AC /* LMNSpecialLine.h in Headers */,
7417CADF20EF46AD00ED84AC /* UIImage+LMNStore.h in Headers */,
7417CAE720F301E800ED84AC /* LMNLine.h in Headers */,
7450D18F20858C03005BD352 /* LMNToolBar.h in Headers */,
7407768120EA153F0022F99E /* LMNItem.h in Headers */,
7407767520E9FE400022F99E /* LMNStore.h in Headers */,
74BD77122073214B00685100 /* UIFont+LMNote.h in Headers */,
7417CAFB20F3066000ED84AC /* LMNNumberingLine.h in Headers */,
7417CAFF20F306D000ED84AC /* LMNBulletsLine.h in Headers */,
7407768520EA15DA0022F99E /* LMNFolder.h in Headers */,
74C94AC220F72A5A00CC3C27 /* LMNTextStorage+Export.h in Headers */,
740776A420EF39D40022F99E /* NSTextAttachment+LMNStore.h in Headers */,
7407769320EB8CF30022F99E /* LMNDraft.h in Headers */,
7417CB0320F306FF00ED84AC /* LMNImageLine.h in Headers */,
7417CAF320F305EE00ED84AC /* LMNTextLine.h in Headers */,
740122902005E6C80072C387 /* LMNTextView.h in Headers */,
74C94AC620F731DE00CC3C27 /* LMNWebViewController.h in Headers */,
7450D19920883544005BD352 /* LMNImageInputViewController.h in Headers */,
74D4F7B8206DCB71007CFE8D /* LMNLineChain.h in Headers */,
7417CAEB20F3030D00ED84AC /* LMNCheckboxLine.h in Headers */,
740F632820561E23001A6FA9 /* LMNTextStorage.h in Headers */,
7401227B2005E4CF0072C387 /* LMNote.h in Headers */,
747AAC1E208F03D5008FDB52 /* LMNImageView.h in Headers */,
7450D19E20883E84005BD352 /* LMNPhotoCollectionCell.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
740122752005E4CF0072C387 /* LMNote */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7401227E2005E4CF0072C387 /* Build configuration list for PBXNativeTarget "LMNote" */;
buildPhases = (
740122712005E4CF0072C387 /* Sources */,
740122722005E4CF0072C387 /* Frameworks */,
740122732005E4CF0072C387 /* Headers */,
740122742005E4CF0072C387 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = LMNote;
productName = LMNote;
productReference = 740122762005E4CF0072C387 /* LMNote.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7401226D2005E4CF0072C387 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = littleMeaning;
TargetAttributes = {
740122752005E4CF0072C387 = {
CreatedOnToolsVersion = 9.1;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 740122702005E4CF0072C387 /* Build configuration list for PBXProject "LMNote" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 7401226C2005E4CF0072C387;
productRefGroup = 740122772005E4CF0072C387 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
740122752005E4CF0072C387 /* LMNote */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
740122742005E4CF0072C387 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74E9256420D8D71D00383146 /* Feature in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
740122712005E4CF0072C387 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74C94AC320F72A5A00CC3C27 /* LMNTextStorage+Export.m in Sources */,
7407768220EA153F0022F99E /* LMNItem.m in Sources */,
7417CAE820F301E800ED84AC /* LMNLine.m in Sources */,
7417CB0020F306D000ED84AC /* LMNBulletsLine.m in Sources */,
74D4F7B7206DCB71007CFE8D /* LMNLineChain.m in Sources */,
7417CAF820F3060000ED84AC /* LMNSpecialLine.m in Sources */,
7407767620E9FE400022F99E /* LMNStore.m in Sources */,
7417CB0820F30DAF00ED84AC /* LMNLineChain+Numbering.m in Sources */,
7450D19020858C03005BD352 /* LMNToolBar.m in Sources */,
7407769420EB8CF30022F99E /* LMNDraft.m in Sources */,
7417CAFC20F3066000ED84AC /* LMNNumberingLine.m in Sources */,
74C94AC720F731DE00CC3C27 /* LMNWebViewController.m in Sources */,
7417CB0420F306FF00ED84AC /* LMNImageLine.m in Sources */,
7417CAEC20F3030D00ED84AC /* LMNCheckboxLine.m in Sources */,
747AAC1F208F03D5008FDB52 /* LMNImageView.m in Sources */,
740F632920561E23001A6FA9 /* LMNTextStorage.m in Sources */,
740122912005E6C80072C387 /* LMNTextView.m in Sources */,
74BD77112073214B00685100 /* UIFont+LMNote.m in Sources */,
7407768620EA15DA0022F99E /* LMNFolder.m in Sources */,
7450D19A20883544005BD352 /* LMNImageInputViewController.m in Sources */,
740776A520EF39D40022F99E /* NSTextAttachment+LMNStore.m in Sources */,
7417CAF420F305EE00ED84AC /* LMNTextLine.m in Sources */,
7450D19D20883E83005BD352 /* LMNPhotoCollectionCell.m in Sources */,
7417CAE020EF46AD00ED84AC /* UIImage+LMNStore.m in Sources */,
7450D195208596FC005BD352 /* LMNoteViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
7401227C2005E4CF0072C387 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
7401227D2005E4CF0072C387 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
7401227F2005E4CF0072C387 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = L99F5K8V47;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lm.LMNote;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
740122802005E4CF0072C387 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = L99F5K8V47;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.lm.LMNote;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
740122702005E4CF0072C387 /* Build configuration list for PBXProject "LMNote" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7401227C2005E4CF0072C387 /* Debug */,
7401227D2005E4CF0072C387 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7401227E2005E4CF0072C387 /* Build configuration list for PBXNativeTarget "LMNote" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7401227F2005E4CF0072C387 /* Debug */,
740122802005E4CF0072C387 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7401226D2005E4CF0072C387 /* Project object */;
}
================================================
FILE: LMNote/LMNote.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:LMNote.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: LMNote/LMNote.xcodeproj/xcshareddata/xcschemes/LMNote.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "740122752005E4CF0072C387"
BuildableName = "LMNote.framework"
BlueprintName = "LMNote"
ReferencedContainer = "container:LMNote.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "740122752005E4CF0072C387"
BuildableName = "LMNote.framework"
BlueprintName = "LMNote"
ReferencedContainer = "container:LMNote.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "740122752005E4CF0072C387"
BuildableName = "LMNote.framework"
BlueprintName = "LMNote"
ReferencedContainer = "container:LMNote.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: LMNote/Supporting Files/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.9</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
================================================
FILE: LMNote.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:LMNote/LMNote.xcodeproj">
</FileRef>
<FileRef
location = "group:LMNoteDemo.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: LMNote.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: LMNoteDemo/AppDelegate.h
================================================
//
// AppDelegate.h
// LMNoteDemo
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
================================================
FILE: LMNoteDemo/AppDelegate.m
================================================
//
// AppDelegate.m
// LMNoteDemo
//
// Created by littleMeaning on 2018/1/10.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "AppDelegate.h"
#import "FolderViewController.h"
@import LMNote;
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
FolderViewController *rootViewController = [[FolderViewController alloc] init];
UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = navi;
[self.window makeKeyAndVisible];
return YES;
}
@end
================================================
FILE: LMNoteDemo/FolderViewController.h
================================================
//
// ViewController.h
// LMNoteDemo
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@class LMNFolder;
@interface FolderViewController : UITableViewController
@property (nonatomic, strong, readonly) LMNFolder *folder;
@end
================================================
FILE: LMNoteDemo/FolderViewController.m
================================================
//
// ViewController.m
// LMNoteDemo
//
// Created by littleMeaning on 2018/7/2.
// Copyright © 2018年 littleMeaning. All rights reserved.
//
#import "FolderViewController.h"
@import LMNote;
@interface FolderViewController ()
@property (nonatomic, strong, readwrite) LMNFolder *folder;
@end
@implementation FolderViewController
- (instancetype)init
{
return [self initWithFolder:nil];
}
- (instancetype)initWithFolder:(LMNFolder *)folder
{
self = [super init];
if (self) {
self.folder = folder ?: [LMNStore shared].rootFolder;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIBarButtonItem *itemAdd = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"baritem_folder"] style:UIBarButtonItemStylePlain target:self action:@selector(add)];
UIBarButtonItem *itemEdit = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(edit)];
self.navigationItem.rightBarButtonItems = @[itemEdit, itemAdd];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storeChanged:) name:LMNStoreDidChangedNotification object:nil];
}
- (void)add
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"创建目录" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSString *name = alert.textFields.firstObject.text;
LMNFolder *folder = [[LMNFolder alloc] initWithUUID:[NSUUID UUID] name:name date:[NSDate date]];
[self.folder add:folder];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:confirm];
[alert addAction:cancel];
[alert addTextFieldWithConfigurationHandler:nil];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)edit
{
LMNDraft *draft = [[LMNDraft alloc] initWithUUID:[NSUUID UUID] name:@"" date:[NSDate date]];
draft.parent = self.folder;
LMNoteViewController *vc = [[LMNoteViewController alloc] initWithDraft:draft];
[self.navigationController pushViewController:vc animated:YES];
}
- (void)storeChanged:(NSNotification *)notification
{
[[LMNStore shared] reload];
self.folder = [LMNStore shared].rootFolder;
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.folder.contents.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LMNItem *item = self.folder.contents[indexPath.row];
UITableViewCell *cell;
if ([item isKindOfClass:[LMNFolder class]]) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = item.name;
}
else if ([item isKindOfClass:[LMNDraft class]]) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
cell.textLabel.text = item.name;
cell.detailTextLabel.text = item.date.description;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
LMNItem *item = self.folder.contents[indexPath.row];
if ([item isKindOfClass:[LMNFolder class]]) {
FolderViewController *vc = [[FolderViewController alloc] initWithFolder:(LMNFolder *)item];
[self.navigationController pushViewController:vc animated:YES];
}
else if ([item isKindOfClass:[LMNDraft class]]) {
LMNoteViewController *vc = [[LMNoteViewController alloc] initWithDraft:(LMNDraft *)item];
[self.navigationController pushViewController:vc animated:YES];
}
}
@end
================================================
FILE: LMNoteDemo/Util/LMColorPickerView.h
================================================
//
// LMColorPickerView.h
// LMColorPickerView
//
// Created by Chenly on 16/5/14.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@class LMColorPickerView;
@protocol LMColorPickerViewDataSource <NSObject>
- (NSInteger)lm_numberOfColorsInColorPickerView:(LMColorPickerView *)pickerView;
- (UIColor *)lm_colorPickerView:(LMColorPickerView *)pickerView colorForItemAtIndex:(NSInteger)index;
@end
@protocol LMColorPickerViewDelegate <NSObject>
@optional
- (void)lm_colorPickerView:(LMColorPickerView *)pickerView didSelectIndex:(NSInteger)index;
- (void)lm_colorPickerView:(LMColorPickerView *)pickerView didSelectColor:(UIColor *)color;
@end
@interface LMColorPickerView : UIView
@property (nonatomic, weak) id<LMColorPickerViewDataSource> dataSource;
@property (nonatomic, weak) id<LMColorPickerViewDelegate> delegate;
@property (nonatomic, assign) NSInteger spacingBetweenColors; // default is 20.f
@property (nonatomic, readonly) NSInteger numberOfColors;
@property (nonatomic, readonly) NSInteger selectedIndex;
- (void)reloadData;
- (void)selectIndex:(NSInteger)index;
@end
================================================
FILE: LMNoteDemo/Util/LMColorPickerView.m
================================================
//
// LMColorPickerView.m
// LMColorPickerView
//
// Created by Chenly on 16/5/14.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import "LMColorPickerView.h"
@interface LMColorPickerView ()
@end
@implementation LMColorPickerView
{
UIScrollView *_scrollView;
NSMutableArray *_itemViews;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self setup];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
- (void)setup {
self.backgroundColor = [UIColor whiteColor];
_scrollView = [[UIScrollView alloc] init];
_scrollView.showsHorizontalScrollIndicator = NO;
[self addSubview:_scrollView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self addGestureRecognizer:tap];
_itemViews = [NSMutableArray array];
_selectedIndex = -1;
_spacingBetweenColors = 20.f;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect rect = CGRectZero;
rect.size.height = (CGRectGetHeight(self.bounds) - self.spacingBetweenColors) / 2;
rect.size.width = rect.size.height;
NSInteger numberOfColorsInRow = self.numberOfColors / 2 + (self.numberOfColors % 2);
CGFloat contentWidth = numberOfColorsInRow * CGRectGetWidth(rect) + (numberOfColorsInRow - 1) * self.spacingBetweenColors;
if (contentWidth < CGRectGetWidth(self.bounds)) {
// 两行时无法占满宽度,则用一行展示。
rect.origin.y = self.spacingBetweenColors;
rect.size.height = CGRectGetHeight(self.bounds) - self.spacingBetweenColors * 2;
rect.size.width = rect.size.height;
numberOfColorsInRow = self.numberOfColors;
}
for (NSInteger index = 0; index < _itemViews.count; index++) {
if (index == numberOfColorsInRow) {
rect.origin.x = 0;
rect.origin.y += CGRectGetHeight(rect) + self.spacingBetweenColors;
}
UIView *itemView = _itemViews[index];
CGAffineTransform transform = itemView.transform;
itemView.transform = CGAffineTransformIdentity;
itemView.frame = rect;
itemView.layer.cornerRadius = CGRectGetWidth(rect) / 2;
itemView.transform = transform;
rect.origin.x += CGRectGetWidth(rect) + self.spacingBetweenColors;
}
CGSize contentSize = CGSizeZero;
contentSize.width = numberOfColorsInRow * CGRectGetWidth(rect) + (numberOfColorsInRow - 1) * self.spacingBetweenColors;
contentSize.height = CGRectGetHeight(self.bounds);
_scrollView.contentSize = contentSize;
_scrollView.frame = self.bounds;
}
- (void)setDataSource:(id<LMColorPickerViewDataSource>)dataSource {
if (_dataSource == dataSource) {
[self setNeedsLayout];
return;
}
_dataSource = dataSource;
[self reloadData];
}
- (void)reloadData {
[_itemViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[_itemViews removeAllObjects];
_selectedIndex = -1;
if (!self.dataSource) {
return;
}
for (NSInteger index = 0; index < self.numberOfColors; index++) {
UIView *itemView = [[UIView alloc] init];
itemView.transform = CGAffineTransformMakeScale(0.6, 0.6);
itemView.backgroundColor = [self.dataSource lm_colorPickerView:self colorForItemAtIndex:index];
[_scrollView addSubview:itemView];
[_itemViews addObject:itemView];
}
[self setNeedsLayout];
}
- (NSInteger)numberOfColors {
return [self.dataSource lm_numberOfColorsInColorPickerView:self];
}
#pragma mark - tap
- (void)handleTap:(UITapGestureRecognizer *)tap {
CGPoint point = [tap locationInView:_scrollView];
for (NSInteger index = 0; index < _itemViews.count; index++) {
UIView *itemView = _itemViews[index];
if (CGRectContainsPoint(itemView.frame, point)) {
[self selectIndex:index];
if ([self.delegate respondsToSelector:@selector(lm_colorPickerView:didSelectIndex:)]) {
[self.delegate lm_colorPickerView:self didSelectIndex:index];
}
if ([self.delegate respondsToSelector:@selector(lm_colorPickerView:didSelectColor:)]) {
[self.delegate lm_colorPickerView:self didSelectColor:itemView.backgroundColor];
}
break;
}
}
}
#pragma mark -
- (void)selectIndex:(NSInteger)index {
if (_selectedIndex >= 0 && _selectedIndex < _itemViews.count) {
UIView *lastSelectedView = _itemViews[_selectedIndex];
lastSelectedView.transform = CGAffineTransformMakeScale(0.6, 0.6);
}
_selectedIndex = index;
UIView *selectedLabel = _itemViews[_selectedIndex];
selectedLabel.transform = CGAffineTransformMakeScale(1.f, 1.f);
}
@end
================================================
FILE: LMNoteDemo/Util/LMTextHTMLParser.h
================================================
//
// LMTextHTMLParser.h
// SimpleWord
//
// Created by Chenly on 16/6/27.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface LMTextHTMLParser : NSObject
+ (NSString *)HTMLFromAttributedString:(NSAttributedString *)attributedString;
@end
================================================
FILE: LMNoteDemo/Util/LMTextHTMLParser.m
================================================
//
// LMTextHTMLParser.m
// SimpleWord
//
// Created by Chenly on 16/6/27.
// Copyright © 2016年 Little Meaning. All rights reserved.
//
#import "LMTextHTMLParser.h"
#import "UIFont+LMText.h"
#import "NSTextAttachment+LMText.h"
@implementation LMTextHTMLParser
/**
* 将原生的 AttributedString 导出成 HTML,用于 Web 端显示,可以根据自己需求修改导出的 HTML 内容,比如添加 class 或是 id 等。
*
* @param attributedString 需要被导出的富文本
*
* @return 导出的 HTML
*/
+ (NSString *)HTMLFromAttributedString:(NSAttributedString *)attributedString {
BOOL isNewParagraph = YES;
NSMutableString *htmlContent = [NSMutableString string];
NSRange effectiveRange = NSMakeRange(0, 0);
while (effectiveRange.location + effectiveRange.length < attributedString.length) {
NSDictionary *attributes = [attributedString attributesAtIndex:effectiveRange.location effectiveRange:&effectiveRange];
NSTextAttachment *attachment = attributes[@"NSAttachment"];
if (attachment) {
switch (attachment.attachmentType) {
case LMTextAttachmentTypeImage:
[htmlContent appendString:[NSString stringWithFormat:@"<img src=\"%@\" width=\"100%%\"/>", attachment.userInfo]];
break;
default:
break;
}
}
else {
NSString *text = [[attributedString string] substringWithRange:effectiveRange];
UIFont *font = attributes[NSFontAttributeName];
UIColor *fontColor = attributes[@"NSColor"];
NSString *color = [self hexStringWithColor:fontColor];
BOOL underline = [attributes[NSUnderlineStyleAttributeName] boolValue];
BOOL isFirst = YES;
NSArray *components = [text componentsSeparatedByString:@"\n"];
for (NSInteger i = 0; i < components.count; i ++) {
NSString *content = components[i];
if (!isFirst && !isNewParagraph) {
[htmlContent appendString:@"</p>"];
isNewParagraph = YES;
}
if (isNewParagraph && (content.length > 0 || i < components.count - 1)) {
// [htmlContent appendString:[NSString stringWithFormat:@"<p style=\"text-indent:%@em;margin:4px auto 0px auto;\">", @(2 * paragraphConfig.indentLevel).stringValue]];
isNewParagraph = NO;
}
[htmlContent appendString:[self HTMLWithContent:content font:font underline:underline color:color]];
isFirst = NO;
}
if (effectiveRange.location + effectiveRange.length >= attributedString.length && ![htmlContent hasSuffix:@"</p>"]) {
// 补上</p>
[htmlContent appendString:@"</p>"];
}
}
effectiveRange = NSMakeRange(effectiveRange.location + effectiveRange.length, 0);
}
return [htmlContent copy];
}
+ (NSString *)HTMLWithContent:(NSString *)content font:(UIFont *)font underline:(BOOL)underline color:(NSString *)color {
if (content.length == 0) {
return @"";
}
if (font.bold) {
content = [NSString stringWithFormat:@"<b>%@</b>", content];
}
if (font.italic) {
content = [NSString stringWithFormat:@"<i>%@</i>", content];
}
if (underline) {
content = [NSString stringWithFormat:@"<u>%@</u>", content];
}
return [NSString stringWithFormat:@"<font style=\"font-size:%f;color:%@\">%@</font>", font.fontSize, color, content];
}
+ (NSString *)hexStringWithColor:(UIColor *)color {
NSString *colorString = [[CIColor colorWithCGColor:color.CGColor] stringRepresentation];
NSArray *parts = [colorString componentsSeparatedByString:@" "];
NSMutableString *hexString = [NSMutableString stringWithString:@"#"];
for (int i = 0; i < 3; i ++) {
[hexString appendString:[NSString stringWithFormat:@"%02X", (int)([parts[i] floatValue] * 255)]];
}
return [hexString copy];
}
@end
================================================
FILE: LMNoteDemo.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
740122602005E4AA0072C387 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7401225F2005E4AA0072C387 /* Assets.xcassets */; };
740122632005E4AA0072C387 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 740122612005E4AA0072C387 /* LaunchScreen.storyboard */; };
740122662005E4AA0072C387 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 740122652005E4AA0072C387 /* main.m */; };
7407769920EBA2930022F99E /* FolderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7407769820EBA2930022F99E /* FolderViewController.m */; };
7425942320063B7600E5F309 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7425942220063B7600E5F309 /* AppDelegate.m */; };
74264B642063765B0062E669 /* LMNote.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 742594042005E84F00E5F309 /* LMNote.framework */; };
74264B652063765B0062E669 /* LMNote.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 742594042005E84F00E5F309 /* LMNote.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
74264B662063765B0062E669 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
74264B652063765B0062E669 /* LMNote.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
740122532005E4AA0072C387 /* LMNoteDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LMNoteDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
7401225F2005E4AA0072C387 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
740122622005E4AA0072C387 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
740122642005E4AA0072C387 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
740122652005E4AA0072C387 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
7407767020E9D0860022F99E /* FolderViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FolderViewController.h; sourceTree = "<group>"; };
7407769820EBA2930022F99E /* FolderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FolderViewController.m; sourceTree = "<group>"; };
742594042005E84F00E5F309 /* LMNote.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LMNote.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7425942120063B7600E5F309 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7425942220063B7600E5F309 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
740122502005E4AA0072C387 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
74264B642063765B0062E669 /* LMNote.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7401224A2005E4AA0072C387 = {
isa = PBXGroup;
children = (
740122552005E4AA0072C387 /* LMNoteDemo */,
7425942420063B9100E5F309 /* Supporting Files */,
740122542005E4AA0072C387 /* Products */,
740122882005E62C0072C387 /* Frameworks */,
);
sourceTree = "<group>";
};
740122542005E4AA0072C387 /* Products */ = {
isa = PBXGroup;
children = (
740122532005E4AA0072C387 /* LMNoteDemo.app */,
);
name = Products;
sourceTree = "<group>";
};
740122552005E4AA0072C387 /* LMNoteDemo */ = {
isa = PBXGroup;
children = (
7425942120063B7600E5F309 /* AppDelegate.h */,
7425942220063B7600E5F309 /* AppDelegate.m */,
7407767020E9D0860022F99E /* FolderViewController.h */,
7407769820EBA2930022F99E /* FolderViewController.m */,
);
path = LMNoteDemo;
sourceTree = "<group>";
};
740122882005E62C0072C387 /* Frameworks */ = {
isa = PBXGroup;
children = (
742594042005E84F00E5F309 /* LMNote.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
7425942420063B9100E5F309 /* Supporting Files */ = {
isa = PBXGroup;
children = (
7401225F2005E4AA0072C387 /* Assets.xcassets */,
740122612005E4AA0072C387 /* LaunchScreen.storyboard */,
740122642005E4AA0072C387 /* Info.plist */,
740122652005E4AA0072C387 /* main.m */,
);
path = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
740122522005E4AA0072C387 /* LMNoteDemo */ = {
isa = PBXNativeTarget;
buildConfigurationList = 740122692005E4AA0072C387 /* Build configuration list for PBXNativeTarget "LMNoteDemo" */;
buildPhases = (
7401224F2005E4AA0072C387 /* Sources */,
740122502005E4AA0072C387 /* Frameworks */,
740122512005E4AA0072C387 /* Resources */,
74264B662063765B0062E669 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = LMNoteDemo;
productName = LMNoteDemo;
productReference = 740122532005E4AA0072C387 /* LMNoteDemo.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7401224B2005E4AA0072C387 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = littleMeaning;
TargetAttributes = {
740122522005E4AA0072C387 = {
CreatedOnToolsVersion = 9.1;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 7401224E2005E4AA0072C387 /* Build configuration list for PBXProject "LMNoteDemo" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7401224A2005E4AA0072C387;
productRefGroup = 740122542005E4AA0072C387 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
740122522005E4AA0072C387 /* LMNoteDemo */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
740122512005E4AA0072C387 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
740122632005E4AA0072C387 /* LaunchScreen.storyboard in Resources */,
740122602005E4AA0072C387 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7401224F2005E4AA0072C387 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7407769920EBA2930022F99E /* FolderViewController.m in Sources */,
7425942320063B7600E5F309 /* AppDelegate.m in Sources */,
740122662005E4AA0072C387 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
740122612005E4AA0072C387 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
740122622005E4AA0072C387 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
740122672005E4AA0072C387 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
740122682005E4AA0072C387 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7401226A2005E4AA0072C387 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = L99F5K8V47;
INFOPLIST_FILE = "Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lm.LMNoteDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 armv7 armv7s";
};
name = Debug;
};
7401226B2005E4AA0072C387 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = L99F5K8V47;
INFOPLIST_FILE = "Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.lm.LMNoteDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 armv7 armv7s";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7401224E2005E4AA0072C387 /* Build configuration list for PBXProject "LMNoteDemo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
740122672005E4AA0072C387 /* Debug */,
740122682005E4AA0072C387 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
740122692005E4AA0072C387 /* Build configuration list for PBXNativeTarget "LMNoteDemo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7401226A2005E4AA0072C387 /* Debug */,
7401226B2005E4AA0072C387 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7401224B2005E4AA0072C387 /* Project object */;
}
================================================
FILE: LMNoteDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:LMNoteDemo.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: README.md
================================================

## 介绍
LMNote 是原生代码写的富文本编辑器,支持如下功能:
1. 粗体、下划线、删除线
2. 标题、副标题、正文格式
3. 项目符号、有序列表、检查框段落样式
4. 居左、居中、居右
5. 插入图片
6. 草稿功能
7. 导出成 HTML
### 存在问题
由于开发和维护时间较少,仅开发了一个 Demo,未做良好封装且存在一些问题(短期内可能不会解决),但基本功能完整而且支持扩展,各位可以依据自己需要直接拿取所需的代码进行二次开发。
**已知问题:**
1. 拼音输入过程中可能会有布局错误
2. 输入内容中包含 emoji 时布局错误
3. 图片前后输入文字显示错误
4. 粘贴或是全选删除可能出现意外情况
**未完成内容:**
1. 斜体
2. 导出 HTML 上传图片部分代码
3. 草稿存储图片优化
================================================
FILE: Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x
gitextract_xie_f35z/
├── .gitignore
├── LICENSE
├── LMNote/
│ ├── LMNote/
│ │ ├── .gitignore
│ │ ├── Feature
│ │ ├── LMNote.h
│ │ ├── LMNoteViewController.h
│ │ ├── LMNoteViewController.m
│ │ ├── Lines/
│ │ │ ├── LMNBulletsLine.h
│ │ │ ├── LMNBulletsLine.m
│ │ │ ├── LMNCheckboxLine.h
│ │ │ ├── LMNCheckboxLine.m
│ │ │ ├── LMNImageLine.h
│ │ │ ├── LMNImageLine.m
│ │ │ ├── LMNLine.h
│ │ │ ├── LMNLine.m
│ │ │ ├── LMNLineChain+Numbering.h
│ │ │ ├── LMNLineChain+Numbering.m
│ │ │ ├── LMNLineChain.h
│ │ │ ├── LMNLineChain.m
│ │ │ ├── LMNLineModes.h
│ │ │ ├── LMNNumberingLine.h
│ │ │ ├── LMNNumberingLine.m
│ │ │ ├── LMNSpecialLine.h
│ │ │ ├── LMNSpecialLine.m
│ │ │ ├── LMNTextLine.h
│ │ │ └── LMNTextLine.m
│ │ ├── OtherViews/
│ │ │ ├── LMNImageInputViewController.h
│ │ │ ├── LMNImageInputViewController.m
│ │ │ ├── LMNImageView.h
│ │ │ ├── LMNImageView.m
│ │ │ ├── LMNPhotoCollectionCell.h
│ │ │ ├── LMNPhotoCollectionCell.m
│ │ │ ├── LMNToolBar.h
│ │ │ ├── LMNToolBar.m
│ │ │ ├── LMNWebViewController.h
│ │ │ └── LMNWebViewController.m
│ │ ├── Store/
│ │ │ ├── LMNDraft.h
│ │ │ ├── LMNDraft.m
│ │ │ ├── LMNFolder.h
│ │ │ ├── LMNFolder.m
│ │ │ ├── LMNItem.h
│ │ │ ├── LMNItem.m
│ │ │ ├── LMNStore.h
│ │ │ ├── LMNStore.m
│ │ │ ├── NSTextAttachment+LMNStore.h
│ │ │ ├── NSTextAttachment+LMNStore.m
│ │ │ ├── UIImage+LMNStore.h
│ │ │ └── UIImage+LMNStore.m
│ │ └── TextView/
│ │ ├── LMNTextStorage+Export.h
│ │ ├── LMNTextStorage+Export.m
│ │ ├── LMNTextStorage.h
│ │ ├── LMNTextStorage.m
│ │ ├── LMNTextView.h
│ │ ├── LMNTextView.m
│ │ ├── UIFont+LMNote.h
│ │ └── UIFont+LMNote.m
│ ├── LMNote.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── LMNote.xcscheme
│ └── Supporting Files/
│ └── Info.plist
├── LMNote.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ └── IDEWorkspaceChecks.plist
├── LMNoteDemo/
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── FolderViewController.h
│ ├── FolderViewController.m
│ └── Util/
│ ├── LMColorPickerView.h
│ ├── LMColorPickerView.m
│ ├── LMTextHTMLParser.h
│ └── LMTextHTMLParser.m
├── LMNoteDemo.xcodeproj/
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ └── contents.xcworkspacedata
├── README.md
└── Supporting Files/
├── Assets.xcassets/
│ ├── AppIcon.appiconset/
│ │ └── Contents.json
│ ├── Contents.json
│ ├── baritem_folder.imageset/
│ │ └── Contents.json
│ ├── font/
│ │ ├── Contents.json
│ │ ├── lmn_font_bold.imageset/
│ │ │ └── Contents.json
│ │ ├── lmn_font_italic.imageset/
│ │ │ └── Contents.json
│ │ ├── lmn_font_strikethrough.imageset/
│ │ │ └── Contents.json
│ │ └── lmn_font_underline.imageset/
│ │ └── Contents.json
│ ├── lmn_accessory_checkbox.imageset/
│ │ └── Contents.json
│ ├── lmn_accessory_checkbox_.imageset/
│ │ └── Contents.json
│ ├── lmn_alignment_center.imageset/
│ │ └── Contents.json
│ ├── lmn_alignment_left.imageset/
│ │ └── Contents.json
│ ├── lmn_alignment_right.imageset/
│ │ └── Contents.json
│ ├── lmn_btn_edit.imageset/
│ │ └── Contents.json
│ ├── lmn_btn_plus.imageset/
│ │ └── Contents.json
│ ├── lmn_delete.imageset/
│ │ └── Contents.json
│ ├── lmn_left_square.imageset/
│ │ └── Contents.json
│ ├── lmn_list.imageset/
│ │ └── Contents.json
│ ├── lmn_list_checkbox.imageset/
│ │ └── Contents.json
│ ├── lmn_list_dot.imageset/
│ │ └── Contents.json
│ ├── lmn_list_number.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_a.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_close.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_image.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_subtitle.imageset/
│ │ └── Contents.json
│ ├── lmn_tool_t.imageset/
│ │ └── Contents.json
│ └── lmn_tool_title.imageset/
│ └── Contents.json
├── Base.lproj/
│ └── LaunchScreen.storyboard
├── Info.plist
└── main.m
SYMBOL INDEX (1 symbols across 1 files) FILE: LMNote/LMNote/Lines/LMNLineModes.h type NSString (line 12) | typedef NSString * LMNLineMode;
Condensed preview — 103 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (224K chars).
[
{
"path": ".gitignore",
"chars": 1407,
"preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
},
{
"path": "LICENSE",
"chars": 1060,
"preview": "MIT License\n\nCopyright (c) 2018 陈庆明\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
},
{
"path": "LMNote/LMNote/.gitignore",
"chars": 5,
"preview": "TODO\n"
},
{
"path": "LMNote/LMNote/Feature",
"chars": 798,
"preview": "======================================== 待办 ========================================\n\n功能: 颜色 | 视频 | 链接 | 引言\n功能: toolBar "
},
{
"path": "LMNote/LMNote/LMNote.h",
"chars": 451,
"preview": "//\n// LMNote.h\n// LMNote\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMeaning. All rights"
},
{
"path": "LMNote/LMNote/LMNoteViewController.h",
"chars": 351,
"preview": "//\n// NoteViewController.h\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMea"
},
{
"path": "LMNote/LMNote/LMNoteViewController.m",
"chars": 12342,
"preview": "//\n// NoteViewController.m\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMea"
},
{
"path": "LMNote/LMNote/Lines/LMNBulletsLine.h",
"chars": 219,
"preview": "//\n// LMNBulletsLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All"
},
{
"path": "LMNote/LMNote/Lines/LMNBulletsLine.m",
"chars": 1425,
"preview": "//\n// LMNBulletsLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All"
},
{
"path": "LMNote/LMNote/Lines/LMNCheckboxLine.h",
"chars": 275,
"preview": "//\n// LMNCheckboxLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. Al"
},
{
"path": "LMNote/LMNote/Lines/LMNCheckboxLine.m",
"chars": 2458,
"preview": "//\n// LMNCheckboxLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. Al"
},
{
"path": "LMNote/LMNote/Lines/LMNImageLine.h",
"chars": 403,
"preview": "//\n// LMNImageLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All r"
},
{
"path": "LMNote/LMNote/Lines/LMNImageLine.m",
"chars": 1825,
"preview": "//\n// LMNImageLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All r"
},
{
"path": "LMNote/LMNote/Lines/LMNLine.h",
"chars": 1033,
"preview": "//\n// LMNTextLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/3/16.\n// Copyright © 2018年 littleMeaning. All r"
},
{
"path": "LMNote/LMNote/Lines/LMNLine.m",
"chars": 3823,
"preview": "//\n// LMNTextLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/3/16.\n// Copyright © 2018年 littleMeaning. All r"
},
{
"path": "LMNote/LMNote/Lines/LMNLineChain+Numbering.h",
"chars": 299,
"preview": "//\n// LMNLineChain+Numbering.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMean"
},
{
"path": "LMNote/LMNote/Lines/LMNLineChain+Numbering.m",
"chars": 1652,
"preview": "//\n// LMNLineChain+Numbering.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMean"
},
{
"path": "LMNote/LMNote/Lines/LMNLineChain.h",
"chars": 455,
"preview": "//\n// LMNLineChain.h\n// LMNote\n//\n// Created by littleMeaning on 2018/3/17.\n// Copyright © 2018年 littleMeaning. All "
},
{
"path": "LMNote/LMNote/Lines/LMNLineChain.m",
"chars": 2312,
"preview": "//\n// LMNLineChain.m\n// LMNote\n//\n// Created by littleMeaning on 2018/3/17.\n// Copyright © 2018年 littleMeaning. All "
},
{
"path": "LMNote/LMNote/Lines/LMNLineModes.h",
"chars": 618,
"preview": "//\n// LMNLineModes.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/10.\n// Copyright © 2018年 littleMeaning. All "
},
{
"path": "LMNote/LMNote/Lines/LMNNumberingLine.h",
"chars": 273,
"preview": "//\n// LMNNumberingLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. A"
},
{
"path": "LMNote/LMNote/Lines/LMNNumberingLine.m",
"chars": 1893,
"preview": "//\n// LMNNumberingLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. A"
},
{
"path": "LMNote/LMNote/Lines/LMNSpecialLine.h",
"chars": 351,
"preview": "//\n// LMNSpecialLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All"
},
{
"path": "LMNote/LMNote/Lines/LMNSpecialLine.m",
"chars": 1086,
"preview": "//\n// LMNSpecialLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All"
},
{
"path": "LMNote/LMNote/Lines/LMNTextLine.h",
"chars": 261,
"preview": "//\n// LMNTextLine.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All ri"
},
{
"path": "LMNote/LMNote/Lines/LMNTextLine.m",
"chars": 1390,
"preview": "//\n// LMNTextLine.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/9.\n// Copyright © 2018年 littleMeaning. All ri"
},
{
"path": "LMNote/LMNote/OtherViews/LMNImageInputViewController.h",
"chars": 621,
"preview": "//\n// LMNImageInputViewController.h\n// LMNote\n//\n// Created by littleMeaning on 2018/4/19.\n// Copyright © 2018年 litt"
},
{
"path": "LMNote/LMNote/OtherViews/LMNImageInputViewController.m",
"chars": 7555,
"preview": "//\n// LMNImageInputViewController.m\n// LMNote\n//\n// Created by littleMeaning on 2018/4/19.\n// Copyright © 2018年 litt"
},
{
"path": "LMNote/LMNote/OtherViews/LMNImageView.h",
"chars": 939,
"preview": "//\n// LMNImageView.h\n// LMNote\n//\n// Created by littleMeaning on 2018/4/24.\n// Copyright © 2018年 littleMeaning. All "
},
{
"path": "LMNote/LMNote/OtherViews/LMNImageView.m",
"chars": 4783,
"preview": "//\n// LMNImageView.m\n// LMNote\n//\n// Created by littleMeaning on 2018/4/24.\n// Copyright © 2018年 littleMeaning. All "
},
{
"path": "LMNote/LMNote/OtherViews/LMNPhotoCollectionCell.h",
"chars": 392,
"preview": "//\n// LMPhotoCollectionCell.h\n// SimpleWord\n//\n// Created by littleMeaning on 16/5/16.\n// Copyright © 2016年 Little M"
},
{
"path": "LMNote/LMNote/OtherViews/LMNPhotoCollectionCell.m",
"chars": 3736,
"preview": "//\n// LMPhotoCollectionCell.m\n// SimpleWord\n//\n// Created by littleMeaning on 16/5/16.\n// Copyright © 2016年 Little M"
},
{
"path": "LMNote/LMNote/OtherViews/LMNToolBar.h",
"chars": 1434,
"preview": "//\n// LMNToolBar.h\n// LMNote\n//\n// Created by littleMeaning on 2018/4/17.\n// Copyright © 2018年 littleMeaning. All ri"
},
{
"path": "LMNote/LMNote/OtherViews/LMNToolBar.m",
"chars": 17217,
"preview": "//\n// LMNToolBar.m\n// LMNote\n//\n// Created by littleMeaning on 2018/4/17.\n// Copyright © 2018年 littleMeaning. All ri"
},
{
"path": "LMNote/LMNote/OtherViews/LMNWebViewController.h",
"chars": 276,
"preview": "//\n// LMNWebViewController.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/12.\n// Copyright © 2018年 littleMeani"
},
{
"path": "LMNote/LMNote/OtherViews/LMNWebViewController.m",
"chars": 829,
"preview": "//\n// LMNWebViewController.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/12.\n// Copyright © 2018年 littleMeani"
},
{
"path": "LMNote/LMNote/Store/LMNDraft.h",
"chars": 283,
"preview": "//\n// LMNDraft.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/3.\n// Copyright © 2018年 littleMeaning. All right"
},
{
"path": "LMNote/LMNote/Store/LMNDraft.m",
"chars": 548,
"preview": "//\n// LMNDraft.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/3.\n// Copyright © 2018年 littleMeaning. All right"
},
{
"path": "LMNote/LMNote/Store/LMNFolder.h",
"chars": 335,
"preview": "//\n// LMNFolder.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning. All righ"
},
{
"path": "LMNote/LMNote/Store/LMNFolder.m",
"chars": 1540,
"preview": "//\n// LMNFolder.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning. All righ"
},
{
"path": "LMNote/LMNote/Store/LMNItem.h",
"chars": 596,
"preview": "//\n// LMNItem.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning. All rights"
},
{
"path": "LMNote/LMNote/Store/LMNItem.m",
"chars": 1356,
"preview": "//\n// LMNItem.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning. All rights"
},
{
"path": "LMNote/LMNote/Store/LMNStore.h",
"chars": 628,
"preview": "//\n// LMNStore.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning. All right"
},
{
"path": "LMNote/LMNote/Store/LMNStore.m",
"chars": 2366,
"preview": "//\n// LMNStore.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning. All right"
},
{
"path": "LMNote/LMNote/Store/NSTextAttachment+LMNStore.h",
"chars": 223,
"preview": "//\n// NSTextAttachment+LMNStore.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/6.\n// Copyright © 2018年 littleM"
},
{
"path": "LMNote/LMNote/Store/NSTextAttachment+LMNStore.m",
"chars": 1115,
"preview": "//\n// NSTextAttachment+LMNStore.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/6.\n// Copyright © 2018年 littleM"
},
{
"path": "LMNote/LMNote/Store/UIImage+LMNStore.h",
"chars": 260,
"preview": "//\n// UIImage+LMNStore.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/6.\n// Copyright © 2018年 littleMeaning. A"
},
{
"path": "LMNote/LMNote/Store/UIImage+LMNStore.m",
"chars": 526,
"preview": "//\n// UIImage+LMNStore.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/6.\n// Copyright © 2018年 littleMeaning. A"
},
{
"path": "LMNote/LMNote/TextView/LMNTextStorage+Export.h",
"chars": 291,
"preview": "//\n// LMNTextStorage+Export.h\n// LMNote\n//\n// Created by littleMeaning on 2018/7/12.\n// Copyright © 2018年 littleMean"
},
{
"path": "LMNote/LMNote/TextView/LMNTextStorage+Export.m",
"chars": 5141,
"preview": "//\n// LMNTextStorage+Export.m\n// LMNote\n//\n// Created by littleMeaning on 2018/7/12.\n// Copyright © 2018年 littleMean"
},
{
"path": "LMNote/LMNote/TextView/LMNTextStorage.h",
"chars": 650,
"preview": "//\n// LMNTextStorage.h\n// LMNote\n//\n// Created by littleMeaning on 2018/3/12.\n// Copyright © 2018年 littleMeaning. Al"
},
{
"path": "LMNote/LMNote/TextView/LMNTextStorage.m",
"chars": 7066,
"preview": "//\n// LMNTextStorage.m\n// LMNote\n//\n// Created by littleMeaning on 2018/3/12.\n// Copyright © 2018年 littleMeaning. Al"
},
{
"path": "LMNote/LMNote/TextView/LMNTextView.h",
"chars": 743,
"preview": "//\n// LMNTextView.h\n// LMNote\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMeaning. All r"
},
{
"path": "LMNote/LMNote/TextView/LMNTextView.m",
"chars": 16985,
"preview": "//\n// LMNTextView.m\n// LMNote\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMeaning. All r"
},
{
"path": "LMNote/LMNote/TextView/UIFont+LMNote.h",
"chars": 428,
"preview": "//\n// UIFont+LMNote.h\n// SimpleWord\n//\n// Created by littleMeaning on 16/6/30.\n// Copyright © 2016年 Little Meaning. "
},
{
"path": "LMNote/LMNote/TextView/UIFont+LMNote.m",
"chars": 1118,
"preview": "//\n// UIFont+LMNote.m\n// SimpleWord\n//\n// Created by littleMeaning on 16/6/30.\n// Copyright © 2016年 Little Meaning. "
},
{
"path": "LMNote/LMNote.xcodeproj/project.pbxproj",
"chars": 34894,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "LMNote/LMNote.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 151,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:LMNote.xcodepro"
},
{
"path": "LMNote/LMNote.xcodeproj/xcshareddata/xcschemes/LMNote.xcscheme",
"chars": 2841,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1000\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "LMNote/Supporting Files/Info.plist",
"chars": 774,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "LMNote.xcworkspace/contents.xcworkspacedata",
"chars": 232,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:LMNote/LMNote."
},
{
"path": "LMNote.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "LMNoteDemo/AppDelegate.h",
"chars": 286,
"preview": "//\n// AppDelegate.h\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMeaning. A"
},
{
"path": "LMNoteDemo/AppDelegate.m",
"chars": 787,
"preview": "//\n// AppDelegate.m\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMeaning. A"
},
{
"path": "LMNoteDemo/FolderViewController.h",
"chars": 312,
"preview": "//\n// ViewController.h\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning."
},
{
"path": "LMNoteDemo/FolderViewController.m",
"chars": 4057,
"preview": "//\n// ViewController.m\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/7/2.\n// Copyright © 2018年 littleMeaning."
},
{
"path": "LMNoteDemo/Util/LMColorPickerView.h",
"chars": 1132,
"preview": "//\n// LMColorPickerView.h\n// LMColorPickerView\n//\n// Created by Chenly on 16/5/14.\n// Copyright © 2016年 Little Meani"
},
{
"path": "LMNoteDemo/Util/LMColorPickerView.m",
"chars": 4915,
"preview": "//\n// LMColorPickerView.m\n// LMColorPickerView\n//\n// Created by Chenly on 16/5/14.\n// Copyright © 2016年 Little Meani"
},
{
"path": "LMNoteDemo/Util/LMTextHTMLParser.h",
"chars": 291,
"preview": "//\n// LMTextHTMLParser.h\n// SimpleWord\n//\n// Created by Chenly on 16/6/27.\n// Copyright © 2016年 Little Meaning. All "
},
{
"path": "LMNoteDemo/Util/LMTextHTMLParser.m",
"chars": 4063,
"preview": "//\n// LMTextHTMLParser.m\n// SimpleWord\n//\n// Created by Chenly on 16/6/27.\n// Copyright © 2016年 Little Meaning. All "
},
{
"path": "LMNoteDemo.xcodeproj/project.pbxproj",
"chars": 14211,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "LMNoteDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 155,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:LMNoteDemo.xcod"
},
{
"path": "README.md",
"chars": 387,
"preview": "\n\n## 介绍\nLMNote 是原生代码写的富文本编辑器,支持如下功能:\n\n1. 粗体、下划线、删除线\n2. 标题、副标题、正文格式\n3. 项目符号、有序列表、检查框段落样式\n4. 居左、居中、居右\n5. "
},
{
"path": "Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1590,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"size\" : \"20x20\",\n \"scale\" : \"2x\"\n },\n {\n \"idiom\""
},
{
"path": "Supporting Files/Assets.xcassets/Contents.json",
"chars": 62,
"preview": "{\n \"info\" : {\n \"version\" : 1,\n \"author\" : \"xcode\"\n }\n}"
},
{
"path": "Supporting Files/Assets.xcassets/baritem_folder.imageset/Contents.json",
"chars": 427,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/font/Contents.json",
"chars": 62,
"preview": "{\n \"info\" : {\n \"version\" : 1,\n \"author\" : \"xcode\"\n }\n}"
},
{
"path": "Supporting Files/Assets.xcassets/font/lmn_font_bold.imageset/Contents.json",
"chars": 356,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/font/lmn_font_italic.imageset/Contents.json",
"chars": 360,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/font/lmn_font_strikethrough.imageset/Contents.json",
"chars": 374,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/font/lmn_font_underline.imageset/Contents.json",
"chars": 366,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_accessory_checkbox.imageset/Contents.json",
"chars": 374,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_accessory_checkbox_.imageset/Contents.json",
"chars": 376,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_alignment_center.imageset/Contents.json",
"chars": 370,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_alignment_left.imageset/Contents.json",
"chars": 366,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_alignment_right.imageset/Contents.json",
"chars": 368,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_btn_edit.imageset/Contents.json",
"chars": 354,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_btn_plus.imageset/Contents.json",
"chars": 354,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_delete.imageset/Contents.json",
"chars": 350,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_left_square.imageset/Contents.json",
"chars": 360,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_list.imageset/Contents.json",
"chars": 346,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_list_checkbox.imageset/Contents.json",
"chars": 364,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_list_dot.imageset/Contents.json",
"chars": 354,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_list_number.imageset/Contents.json",
"chars": 360,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_tool_a.imageset/Contents.json",
"chars": 350,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_tool_close.imageset/Contents.json",
"chars": 358,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_tool_image.imageset/Contents.json",
"chars": 358,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_tool_subtitle.imageset/Contents.json",
"chars": 364,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_tool_t.imageset/Contents.json",
"chars": 350,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Assets.xcassets/lmn_tool_title.imageset/Contents.json",
"chars": 358,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n \"idiom\" : \"universal\",\n "
},
{
"path": "Supporting Files/Base.lproj/LaunchScreen.storyboard",
"chars": 1844,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
},
{
"path": "Supporting Files/Info.plist",
"chars": 1473,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Supporting Files/main.m",
"chars": 344,
"preview": "//\n// main.m\n// LMNoteDemo\n//\n// Created by littleMeaning on 2018/1/10.\n// Copyright © 2018年 littleMeaning. All righ"
}
]
About this extraction
This page contains the full source code of the littleMeaning/LMNote GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 103 files (197.5 KB), approximately 59.5k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.