├── .gitignore ├── LICENSE ├── LMNote.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LMNote ├── LMNote.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── LMNote.xcscheme ├── 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 └── Supporting Files │ └── Info.plist ├── LMNoteDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── LMNoteDemo ├── AppDelegate.h ├── AppDelegate.m ├── FolderViewController.h ├── FolderViewController.m └── Util │ ├── LMColorPickerView.h │ ├── LMColorPickerView.m │ ├── LMTextHTMLParser.h │ └── LMTextHTMLParser.m ├── README.gif ├── README.md └── Supporting Files ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── baritem_folder.imageset │ ├── Contents.json │ ├── baritem_folder@2x.png │ └── baritem_folder@3x.png ├── font │ ├── Contents.json │ ├── lmn_font_bold.imageset │ │ ├── Contents.json │ │ ├── lmn_font_bold@2x.png │ │ └── lmn_font_bold@3x.png │ ├── lmn_font_italic.imageset │ │ ├── Contents.json │ │ ├── lmn_font_italic@2x.png │ │ └── lmn_font_italic@3x.png │ ├── lmn_font_strikethrough.imageset │ │ ├── Contents.json │ │ ├── lmn_font_strikethrough@2x.png │ │ └── lmn_font_strikethrough@3x.png │ └── lmn_font_underline.imageset │ │ ├── Contents.json │ │ ├── lmn_font_underline@2x.png │ │ └── lmn_font_underline@3x.png ├── lmn_accessory_checkbox.imageset │ ├── Contents.json │ ├── lmn_accessory_checkbox@2x.png │ └── lmn_accessory_checkbox@3x.png ├── lmn_accessory_checkbox_.imageset │ ├── Contents.json │ ├── lmn_accessory_checkbox_@2x.png │ └── lmn_accessory_checkbox_@3x.png ├── lmn_alignment_center.imageset │ ├── Contents.json │ ├── lmn_alignment_center@2x.png │ └── lmn_alignment_center@3x.png ├── lmn_alignment_left.imageset │ ├── Contents.json │ ├── lmn_alignment_left@2x.png │ └── lmn_alignment_left@3x.png ├── lmn_alignment_right.imageset │ ├── Contents.json │ ├── lmn_alignment_right@2x.png │ └── lmn_alignment_right@3x.png ├── lmn_btn_edit.imageset │ ├── Contents.json │ ├── lmn_btn_edit@2x.png │ └── lmn_btn_edit@3x.png ├── lmn_btn_plus.imageset │ ├── Contents.json │ ├── lmn_btn_plus@2x.png │ └── lmn_btn_plus@3x.png ├── lmn_delete.imageset │ ├── Contents.json │ ├── lmn_delete@2x.png │ └── lmn_delete@3x.png ├── lmn_left_square.imageset │ ├── Contents.json │ ├── lmn_left_square@2x.png │ └── lmn_left_square@3x.png ├── lmn_list.imageset │ ├── Contents.json │ ├── lmn_list@2x.png │ └── lmn_list@3x.png ├── lmn_list_checkbox.imageset │ ├── Contents.json │ ├── lmn_list_checkbox@2x.png │ └── lmn_list_checkbox@3x.png ├── lmn_list_dot.imageset │ ├── Contents.json │ ├── lmn_list_dot@2x.png │ └── lmn_list_dot@3x.png ├── lmn_list_number.imageset │ ├── Contents.json │ ├── lmn_list_number@2x.png │ └── lmn_list_number@3x.png ├── lmn_tool_a.imageset │ ├── Contents.json │ ├── lmn_tool_a@2x.png │ └── lmn_tool_a@3x.png ├── lmn_tool_close.imageset │ ├── Contents.json │ ├── lmn_tool_close@2x.png │ └── lmn_tool_close@3x.png ├── lmn_tool_image.imageset │ ├── Contents.json │ ├── lmn_tool_image@2x.png │ └── lmn_tool_image@3x.png ├── lmn_tool_subtitle.imageset │ ├── Contents.json │ ├── lmn_tool_subtitle@2x.png │ └── lmn_tool_subtitle@3x.png ├── lmn_tool_t.imageset │ ├── Contents.json │ ├── lmn_tool_t@2x.png │ └── lmn_tool_t@3x.png └── lmn_tool_title.imageset │ ├── Contents.json │ ├── lmn_tool_title@2x.png │ └── lmn_tool_title@3x.png ├── Base.lproj └── LaunchScreen.storyboard ├── Info.plist └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 陈庆明 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LMNote.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LMNote.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LMNote/LMNote.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LMNote/LMNote.xcodeproj/xcshareddata/xcschemes/LMNote.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /LMNote/LMNote/.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /LMNote/LMNote/Feature: -------------------------------------------------------------------------------- 1 | ======================================== 待办 ======================================== 2 | 3 | 功能: 颜色 | 视频 | 链接 | 引言 4 | 功能: toolBar 适配(通过 … 实现) 5 | 6 | bug: emoji 高度计算错误 7 | bug: 拼音输入过程中高度计算错误 8 | bug: 找到修改 kLineSpacing 会导致整个计算错误的原因。 9 | 10 | 优化: 草稿,图片缓存优化,没有使用的图片定时清理 11 | 优化: 支持斜体。 12 | 优化: 图片非编辑状态应该收起 13 | 优化: 图片点击之后更丰富的处理 14 | 优化: 在选图片 collectionView 上下拉,选中状态会闪烁 15 | 优化: 图片前后输入文字处理。 16 | 17 | ======================================== 完成 ======================================== 18 | 19 | 功能: 草稿 20 | 功能: 导出成 HTML 21 | 22 | bug: checkbox 后插入图片计算错误 23 | bug: 首行切换 checkbox 后换行显示错误 24 | bug: 中间插入图片后位置不正确 25 | bug: 首行图片后位置计算不正确 26 | bug: 删除图片后位置计算不正确 27 | bug: 连续插入图片位置不正确 28 | bug: 粗体换行好计算错误 29 | bug: 中文高度计算错误 30 | bug: 图片插在 numbering 中间 31 | bug: 行高计算小数精确位数影响结果 32 | bug: 粗体和 underline 时候高度错误。 33 | 34 | 优化: bullets 换成图片 35 | 优化: 居中换行到下一行时,光标位置不正确。 36 | 优化: 插入图片时候主线程会卡顿一下,应该图片太大导致。 37 | 优化: 粗体换行后继承。 38 | 优化: numbering 之间间距太大。 39 | 优化: 插入位置为行首时,图片插入在前面 40 | 41 | 重构: Line 的扩展 42 | -------------------------------------------------------------------------------- /LMNote/LMNote/LMNote.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNote.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXPORT double LMNoteVersionNumber; 12 | FOUNDATION_EXPORT const unsigned char LMNoteVersionString[]; 13 | 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | -------------------------------------------------------------------------------- /LMNote/LMNote/LMNoteViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoteViewController.h 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class LMNDraft; 12 | 13 | @interface LMNoteViewController : UIViewController 14 | 15 | @property (nonatomic, readonly) LMNDraft *draft; 16 | 17 | - (instancetype)initWithDraft:(LMNDraft *)draft; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /LMNote/LMNote/LMNoteViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NoteViewController.m 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNoteViewController.h" 10 | #import "LMNTextView.h" 11 | #import "LMNToolBar.h" 12 | #import "UIFont+LMNote.h" 13 | #import "LMNStore.h" 14 | #import "LMNDraft.h" 15 | #import "LMNImageView.h" 16 | #import "LMNImageInputViewController.h" 17 | #import "LMNWebViewController.h" 18 | 19 | @interface LMNoteViewController () 20 | 21 | @property (nonatomic, strong) LMNTextView *textView; 22 | @property (nonatomic, strong) LMNToolBar *toolBar; 23 | @property (nonatomic, strong) UIButton *editButton; 24 | 25 | @property (nonatomic, strong) LMNImageInputViewController *imageInputViewController; 26 | @property (nonatomic, weak) UIViewController *currentInputController; 27 | @property (nonatomic, assign) NSInteger cursorIndex; 28 | 29 | @property (nonatomic, assign) CGFloat keyboardHeight; 30 | 31 | @end 32 | 33 | @implementation LMNoteViewController 34 | 35 | - (instancetype)initWithDraft:(LMNDraft *)draft 36 | { 37 | self = [super init]; 38 | if (self) { 39 | _draft = draft; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)loadSubviews 45 | { 46 | self.textView = ({ 47 | LMNTextView *textView = [[LMNTextView alloc] initWithTextStorage:self.draft.textStorage]; 48 | textView.backgroundColor = [UIColor whiteColor]; 49 | textView.spellCheckingType = UITextSpellCheckingTypeNo; 50 | textView.delegate = self; 51 | textView; 52 | }); 53 | [self.view addSubview:self.textView]; 54 | 55 | self.editButton = ({ 56 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 57 | [button setImage:[UIImage imageNamed:@"lmn_btn_edit"] forState:UIControlStateNormal]; 58 | button.backgroundColor = [UIColor colorWithWhite:.5f alpha:.75f]; 59 | button.clipsToBounds = YES; 60 | [button addTarget:self action:@selector(showToolBar:) forControlEvents:UIControlEventTouchUpInside]; 61 | button; 62 | }); 63 | [self.view addSubview:self.editButton]; 64 | 65 | self.toolBar = ({ 66 | LMNToolBar *toolBar = [LMNToolBar toolBar]; 67 | toolBar.delegate = self; 68 | toolBar.hidden = YES; 69 | toolBar; 70 | }); 71 | [self.view addSubview:self.toolBar]; 72 | } 73 | 74 | - (void)viewDidLoad 75 | { 76 | [super viewDidLoad]; 77 | self.view.backgroundColor = [UIColor whiteColor]; 78 | 79 | [self loadSubviews]; 80 | 81 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"HTML" style:UIBarButtonItemStylePlain target:self action:@selector(export)]; 82 | } 83 | 84 | - (void)layoutTextView 85 | { 86 | CGFloat toolbarHeight = 44.f; 87 | self.textView.frame = self.view.bounds; 88 | self.toolBar.frame = ({ 89 | CGRect rect = self.view.bounds; 90 | rect.size.height = toolbarHeight; 91 | rect.origin.y = CGRectGetHeight(self.view.bounds) - CGRectGetHeight(rect) - self.keyboardHeight; 92 | rect; 93 | }); 94 | UIEdgeInsets insets = self.textView.contentInset; 95 | insets.bottom = self.keyboardHeight + 10.f; 96 | if (self.toolBar.hidden == NO) { 97 | insets.bottom += toolbarHeight; 98 | } 99 | self.textView.contentInset = insets; 100 | } 101 | 102 | - (void)viewDidLayoutSubviews 103 | { 104 | [super viewDidLayoutSubviews]; 105 | [self layoutTextView]; 106 | 107 | self.editButton.frame = ({ 108 | CGRect rect = CGRectZero; 109 | rect.size = CGSizeMake(50.f, 50.f); 110 | rect.origin.x = CGRectGetWidth(self.view.bounds) - 50.f - 20.f; 111 | rect.origin.y = CGRectGetHeight(self.view.bounds) - 50.f - 20.f - self.keyboardHeight; 112 | rect; 113 | }); 114 | self.editButton.layer.cornerRadius = 25.f; 115 | 116 | self.toolBar.frame = ({ 117 | CGRect rect = self.view.bounds; 118 | rect.origin.y = CGRectGetHeight(rect) - 44.f - self.keyboardHeight; 119 | rect.size.height = 44.f; 120 | rect; 121 | }); 122 | } 123 | 124 | - (void)viewWillAppear:(BOOL)animated 125 | { 126 | [super viewWillAppear:animated]; 127 | [self addObservers]; 128 | } 129 | 130 | - (void)viewWillDisappear:(BOOL)animated 131 | { 132 | [super viewWillDisappear:animated]; 133 | [self removeObservers]; 134 | [self saveDraft]; 135 | } 136 | 137 | - (void)addObservers 138 | { 139 | [[NSNotificationCenter defaultCenter] addObserver:self 140 | selector:@selector(keyboardWillShow:) 141 | name:UIKeyboardWillShowNotification 142 | object:nil]; 143 | [[NSNotificationCenter defaultCenter] addObserver:self 144 | selector:@selector(keyboardWillHide:) 145 | name:UIKeyboardWillHideNotification 146 | object:nil]; 147 | } 148 | 149 | - (void)removeObservers 150 | { 151 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 152 | } 153 | 154 | - (void)saveDraft 155 | { 156 | __block NSString *firstLine = nil; 157 | NSCharacterSet *characterSet = [NSCharacterSet whitespaceCharacterSet]; 158 | [self.textView.text enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) { 159 | line = [line stringByTrimmingCharactersInSet:characterSet]; 160 | if (line.length > 0) { 161 | firstLine = line; 162 | *stop = YES; 163 | } 164 | }]; 165 | if (firstLine.length > 0) { 166 | self.draft.name = firstLine; 167 | self.draft.textStorage = (LMNTextStorage *)self.textView.textStorage; 168 | [self.draft save]; 169 | } 170 | else { 171 | [self.draft delete]; 172 | } 173 | } 174 | 175 | - (void)export 176 | { 177 | [self.textView exportHTML:^(BOOL succeed, NSString *html) { 178 | NSLog(@"html:\n%@", html); 179 | LMNWebViewController *vc = [[LMNWebViewController alloc] init]; 180 | vc.html = html; 181 | [self.navigationController pushViewController:vc animated:YES]; 182 | }]; 183 | } 184 | 185 | #pragma mark - input 186 | 187 | - (LMNImageInputViewController *)imageInputViewController 188 | { 189 | if (!_imageInputViewController) { 190 | _imageInputViewController = [[LMNImageInputViewController alloc] init]; 191 | _imageInputViewController.delegate = self; 192 | } 193 | return _imageInputViewController; 194 | } 195 | 196 | - (void)changeTextInputForTag:(LMNToolBarItemTag)tag 197 | { 198 | UIViewController *inputViewController = nil; 199 | switch (tag) { 200 | case LMNToolBarItemTagImage: 201 | inputViewController = self.imageInputViewController; 202 | self.cursorIndex = NSMaxRange(self.textView.selectedRange); 203 | break; 204 | default: 205 | break; 206 | } 207 | if (inputViewController && self.currentInputController != inputViewController) { 208 | CGRect rect = self.view.bounds; 209 | rect.size.height = 260.f; 210 | rect.origin.y = CGRectGetHeight(self.view.bounds) - CGRectGetHeight(rect); 211 | inputViewController.view.frame = rect; 212 | } 213 | self.currentInputController = inputViewController; 214 | 215 | [self.textView resignFirstResponder]; 216 | } 217 | 218 | #pragma mark - private 219 | 220 | - (void)showToolBar:(UIButton *)button 221 | { 222 | self.editButton.hidden = YES; 223 | self.toolBar.hidden = NO; 224 | [self.textView becomeFirstResponder]; 225 | } 226 | 227 | - (void)reloadToolBar 228 | { 229 | NSMutableDictionary *attributes = [self.textView.typingAttributes mutableCopy]; 230 | LMNLineMode mode = [self.textView lineModeForRange:self.textView.selectedRange]; 231 | NSString *selectedStr = [self.textView.text substringWithRange:self.textView.selectedRange]; 232 | BOOL isMultiLine = [selectedStr containsString:@"\n"]; 233 | [self.toolBar reloadDataWithTypingAttributes:attributes mode:mode isMultiLine:isMultiLine]; 234 | } 235 | 236 | - (void)showInputView 237 | { 238 | CGRect rect = self.currentInputController.view.frame; 239 | [self.view addSubview:self.currentInputController.view]; 240 | self.currentInputController.view.frame = CGRectOffset(rect, 0, CGRectGetHeight(rect)); 241 | [UIView animateWithDuration:0.3 animations:^{ 242 | self.currentInputController.view.frame = rect; 243 | }]; 244 | } 245 | 246 | - (void)hideInputView 247 | { 248 | if (self.currentInputController) { 249 | [UIView animateWithDuration:0.3 animations:^{ 250 | CGRect rect = self.currentInputController.view.frame; 251 | self.currentInputController.view.frame = CGRectOffset(rect, 0, CGRectGetHeight(rect)); 252 | } completion:^(BOOL finished) { 253 | [self.currentInputController.view removeFromSuperview]; 254 | self.currentInputController = nil; 255 | }]; 256 | } 257 | [self.view setNeedsLayout]; 258 | } 259 | 260 | #pragma mark - Keyboard 261 | 262 | - (void)keyboardWillShow:(NSNotification *)notification 263 | { 264 | NSDictionary *info = [notification userInfo]; 265 | CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; 266 | if (self.keyboardHeight == keyboardSize.height) { 267 | return; 268 | } 269 | self.keyboardHeight = keyboardSize.height; 270 | [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ 271 | [self layoutTextView]; 272 | } completion:nil]; 273 | } 274 | 275 | - (void)keyboardWillHide:(NSNotification *)notification 276 | { 277 | if (self.keyboardHeight == 0) { 278 | return; 279 | } 280 | self.keyboardHeight = 0; 281 | [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ 282 | [self layoutTextView]; 283 | } completion:nil]; 284 | } 285 | 286 | #pragma mark - 287 | 288 | - (void)textViewDidBeginEditing:(UITextView *)textView 289 | { 290 | self.editButton.hidden = YES; 291 | self.toolBar.hidden = NO; 292 | [self hideInputView]; 293 | } 294 | 295 | - (void)textViewDidEndEditing:(UITextView *)textView 296 | { 297 | self.toolBar.hidden = YES; 298 | if (self.currentInputController) { 299 | [self showInputView]; 300 | } 301 | else { 302 | self.editButton.hidden = NO; 303 | } 304 | [self.view setNeedsLayout]; 305 | } 306 | 307 | - (void)textViewDidChangeSelection:(UITextView *)textView 308 | { 309 | [self reloadToolBar]; 310 | } 311 | 312 | #pragma mark - 313 | 314 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedMode:(LMNLineMode)mode 315 | { 316 | [self.textView setLineMode:mode forRange:self.textView.selectedRange]; 317 | [self reloadToolBar]; 318 | } 319 | 320 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedAttributes:(NSDictionary *)attributes 321 | { 322 | BOOL bold = [attributes[LMFontBoldAttributeName] boolValue]; 323 | BOOL italic = [attributes[LMFontItalicAttributeName] boolValue]; 324 | BOOL underline = [attributes[LMFontUnderlineAttributeName] boolValue]; 325 | BOOL strikethrough = [attributes[LMFontStrikethroughAttributeName] boolValue]; 326 | 327 | NSMutableDictionary *typingAttributes = [self.textView.typingAttributes mutableCopy]; 328 | UIFont *font = typingAttributes[NSFontAttributeName]; 329 | typingAttributes[NSFontAttributeName] = [UIFont fontWithFontSize:font.fontSize bold:bold italic:italic]; 330 | if (underline) { 331 | typingAttributes[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle); 332 | } 333 | else { 334 | [typingAttributes removeObjectForKey:NSUnderlineStyleAttributeName]; 335 | } 336 | if (strikethrough) { 337 | typingAttributes[NSStrikethroughStyleAttributeName] = @(1); 338 | } 339 | else { 340 | [typingAttributes removeObjectForKey:NSStrikethroughStyleAttributeName]; 341 | } 342 | self.textView.typingAttributes = typingAttributes; 343 | [self.textView setAttributesForSelection:typingAttributes]; 344 | } 345 | 346 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedTextAlignment:(NSTextAlignment)alignment 347 | { 348 | [self.textView setTextAlignmentForSelection:alignment]; 349 | } 350 | 351 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didSelectedItemWithTag:(LMNToolBarItemTag)tag 352 | { 353 | [self changeTextInputForTag:tag]; 354 | } 355 | 356 | - (void)lmn_toolBarClose:(LMNToolBar *)toolBar 357 | { 358 | self.editButton.hidden = NO; 359 | self.toolBar.hidden = YES; 360 | [self.textView resignFirstResponder]; 361 | } 362 | 363 | #pragma mark - 364 | 365 | - (void)lmn_imageInput:(LMNImageInputViewController *)viewController didSelectPHAsset:(PHAsset *)asset 366 | { 367 | [self hideInputView]; 368 | self.editButton.hidden = NO; 369 | 370 | CGFloat imageWidth = CGRectGetWidth([UIScreen mainScreen].bounds) - 40.f; 371 | CGSize targetSize = CGSizeMake(imageWidth, imageWidth); 372 | PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; 373 | options.networkAccessAllowed = YES; 374 | __block LMNImageView *imageView = nil; 375 | [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *result, NSDictionary *info) { 376 | 377 | if (!imageView) { 378 | imageView = [self.textView insertImage:result atIndex:self.cursorIndex]; 379 | } 380 | else { 381 | imageView.image = result; 382 | } 383 | }]; 384 | } 385 | 386 | - (void)lmn_imageInputClose:(LMNImageInputViewController *)viewController 387 | { 388 | [self hideInputView]; 389 | self.editButton.hidden = NO; 390 | } 391 | 392 | @end 393 | 394 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNBulletsLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNBulletsLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNSpecialLine.h" 10 | 11 | @interface LMNBulletsLine : LMNSpecialLine 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNBulletsLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNBulletsLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNBulletsLine.h" 10 | 11 | @interface LMNBulletsLine () 12 | 13 | @property (nonatomic, strong) UIImageView *bulletImageView; 14 | 15 | @end 16 | 17 | @implementation LMNBulletsLine 18 | 19 | - (UIImage *)bulletImage 20 | { 21 | static dispatch_once_t onceToken; 22 | static UIImage *image; 23 | dispatch_once(&onceToken, ^{ 24 | CGFloat diameter = 6.f; 25 | CGFloat scale = [UIScreen mainScreen].scale; 26 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), NO, scale); 27 | UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, diameter, diameter)]; 28 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 29 | CGContextAddPath(ctx, path.CGPath); 30 | CGContextSetFillColorWithColor(ctx, [UIColor darkGrayColor].CGColor); 31 | CGContextDrawPath(ctx, kCGPathFill); 32 | image = UIGraphicsGetImageFromCurrentImageContext(); 33 | UIGraphicsEndImageContext(); 34 | }); 35 | return image; 36 | } 37 | 38 | #pragma - left view 39 | 40 | - (void)loadLeftView 41 | { 42 | if (self.bulletImageView) { 43 | return; 44 | } 45 | self.bulletImageView = ({ 46 | UIImageView *imageView = [[UIImageView alloc] initWithImage:self.bulletImage]; 47 | imageView.contentMode = UIViewContentModeCenter; 48 | imageView; 49 | }); 50 | } 51 | 52 | - (UIView *)leftView 53 | { 54 | return self.bulletImageView; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNCheckboxLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNCheckboxLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNSpecialLine.h" 10 | 11 | @interface LMNCheckboxLine : LMNSpecialLine 12 | 13 | @property (nonatomic, assign) BOOL checkboxSelected; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNCheckboxLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNCheckboxLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNCheckboxLine.h" 10 | 11 | @interface LMNCheckboxLine () 12 | 13 | @property (nonatomic, strong) UIButton *checkbox; 14 | 15 | @end 16 | 17 | @implementation LMNCheckboxLine 18 | 19 | static CGFloat const kCheckboxLineHeight = 30.f; 20 | 21 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 22 | { 23 | self = [super initWithCoder:aDecoder]; 24 | if (self) { 25 | self.checkboxSelected = [aDecoder decodeBoolForKey:@"checkboxSelected"]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)encodeWithCoder:(NSCoder *)aCoder 31 | { 32 | [super encodeWithCoder:aCoder]; 33 | [aCoder encodeBool:self.checkboxSelected forKey:@"checkboxSelected"]; 34 | } 35 | 36 | - (NSDictionary *)attributes 37 | { 38 | static NSDictionary *attributes; 39 | static dispatch_once_t onceToken; 40 | dispatch_once(&onceToken, ^{ 41 | attributes = [self attributesWithFont:nil]; 42 | UIFont *font = attributes[NSFontAttributeName]; 43 | CGFloat paragraphSpacing = (kCheckboxLineHeight - roundf(font.lineHeight)) / 2.f; 44 | NSMutableParagraphStyle *paragraphStyle = attributes[NSParagraphStyleAttributeName]; 45 | paragraphStyle.paragraphSpacing = paragraphSpacing; 46 | paragraphStyle.paragraphSpacingBefore = paragraphSpacing; 47 | attributes = [attributes copy]; 48 | }); 49 | return attributes; 50 | } 51 | 52 | #pragma mark - checkbox 53 | 54 | - (void)setCheckboxSelected:(BOOL)checkboxSelected 55 | { 56 | _checkboxSelected = checkboxSelected; 57 | self.checkbox.selected = checkboxSelected; 58 | } 59 | 60 | - (void)selectCheckbox:(id)sender 61 | { 62 | self.checkboxSelected = !self.checkboxSelected; 63 | } 64 | 65 | #pragma - left view 66 | 67 | - (CGSize)intrinsicLeftSize 68 | { 69 | return CGSizeMake(kCheckboxLineHeight, kCheckboxLineHeight); 70 | } 71 | 72 | - (void)loadLeftView 73 | { 74 | if (self.checkbox) { 75 | return; 76 | } 77 | 78 | self.checkbox = ({ 79 | UIImage *imageNormal = [UIImage imageNamed:@"lmn_accessory_checkbox"]; 80 | UIImage *imageSelected = [UIImage imageNamed:@"lmn_accessory_checkbox_"]; 81 | UIButton *checkbox = [UIButton buttonWithType:UIButtonTypeCustom]; 82 | [checkbox setImage:imageNormal forState:UIControlStateNormal]; 83 | [checkbox setImage:imageSelected forState:UIControlStateSelected]; 84 | [checkbox addTarget:self action:@selector(selectCheckbox:) forControlEvents:UIControlEventTouchUpInside]; 85 | checkbox; 86 | }); 87 | [self setCheckboxSelected:_checkboxSelected]; 88 | } 89 | 90 | - (UIView *)leftView 91 | { 92 | return self.checkbox; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNImageLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNImageLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLine.h" 10 | 11 | @interface LMNImageLine : LMNLine 12 | 13 | @property (nonatomic, strong, readonly) UIImage *image; 14 | @property (nonatomic, readonly) LMNImageView *bindingImageView; 15 | - (void)bindImageView:(LMNImageView *)bindingImageView; 16 | - (void)unbindImageView; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNImageLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNImageLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNImageLine.h" 10 | #import "LMNImageView.h" 11 | #import "LMNStore.h" 12 | #import "UIImage+LMNStore.h" 13 | 14 | @interface LMNImageLine () 15 | 16 | @property (nonatomic, strong) UIImage *image; 17 | @property (nonatomic, copy) NSString *imageFile; 18 | 19 | @end 20 | 21 | @implementation LMNImageLine 22 | 23 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 24 | { 25 | self = [super initWithCoder:aDecoder]; 26 | if (self) { 27 | self.imageFile = [aDecoder decodeObjectForKey:@"imageFile"]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)encodeWithCoder:(NSCoder *)aCoder 33 | { 34 | [super encodeWithCoder:aCoder]; 35 | 36 | UIImage *image = self.bindingImageView.image; 37 | if (image) { 38 | if (!image.lmn_fileName) { 39 | NSData *imageData = UIImagePNGRepresentation(image); 40 | NSString *fileName = [NSUUID UUID].UUIDString; 41 | NSURL *fileURL = [[LMNStore shared].imageDirectory URLByAppendingPathComponent:fileName]; 42 | [imageData writeToURL:fileURL atomically:YES]; 43 | image.lmn_fileName = fileName; 44 | } 45 | [aCoder encodeObject:image.lmn_fileName forKey:@"imageFile"]; 46 | } 47 | } 48 | 49 | - (void)bindImageView:(LMNImageView *)bindingImageView 50 | { 51 | _bindingImageView = bindingImageView; 52 | bindingImageView.owner = self; 53 | } 54 | 55 | - (void)unbindImageView 56 | { 57 | _bindingImageView.owner = nil; 58 | _bindingImageView = nil; 59 | } 60 | 61 | - (UIImage *)image 62 | { 63 | if (!_image) { 64 | _image = self.bindingImageView.image; 65 | } 66 | if (!_image && self.imageFile != nil) { 67 | NSURL *fileURL = [[LMNStore shared].imageDirectory URLByAppendingPathComponent:self.imageFile]; 68 | _image = [UIImage imageWithContentsOfFile:fileURL.path]; 69 | _image.lmn_fileName = self.imageFile; 70 | } 71 | return _image; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/3/16. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LMNLineModes.h" 11 | 12 | @class LMNImageView; 13 | @class LMNLineChain; 14 | 15 | @interface LMNLine : NSObject 16 | 17 | @property (nonatomic, assign, readonly) BOOL isRoot; 18 | @property (nonatomic, weak, readonly) LMNLineChain *lineChain; 19 | - (void)makeRootOfLineChain:(LMNLineChain *)lineChain; 20 | 21 | @property (nonatomic, copy, readonly) NSString *uuid; 22 | @property (nonatomic, assign) NSRange range; 23 | 24 | @property (nonatomic, weak, readonly) LMNLine *prev; 25 | @property (nonatomic, strong) LMNLine *next; 26 | 27 | - (NSDictionary *)attributes; 28 | - (NSMutableDictionary *)attributesWithFont:(UIFont *)font; 29 | 30 | - (void)insteadOfLine:(LMNLine *)line; 31 | - (void)inheritFromLine:(LMNLine *)line; 32 | - (void)clean; 33 | 34 | @end 35 | 36 | @interface LMNLine (Mode) 37 | 38 | @property (nonatomic, readonly) LMNLineMode mode; 39 | - (BOOL)isKindOfMode:(LMNLineMode)mode; 40 | 41 | + (instancetype)line; 42 | + (instancetype)lineWithMode:(LMNLineMode)mode; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/3/16. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLine.h" 10 | #import "UIFont+LMNote.h" 11 | #import "LMNLineChain.h" 12 | 13 | LMNLineMode const LMNLineModeContent = @"content"; 14 | LMNLineMode const LMNLineModeTitle = @"title"; 15 | LMNLineMode const LMNLineModeSubTitle = @"subtitle"; 16 | LMNLineMode const LMNLineModeNumbering = @"numbering"; 17 | LMNLineMode const LMNLineModeBullets = @"bullets"; 18 | LMNLineMode const LMNLineModeCheckbox = @"checkbox"; 19 | LMNLineMode const LMNLineModeModeImage = @"image"; 20 | LMNLineMode const LMNLineModeModeUnknown = @"Unknown"; 21 | 22 | static Class lmn_lineClassOf(LMNLineMode mode) 23 | { 24 | NSString *className = [NSString stringWithFormat:@"LMN%@Line", [mode capitalizedString]]; 25 | return NSClassFromString(className); 26 | } 27 | 28 | @interface LMNLine () 29 | 30 | @property (nonatomic, assign) BOOL isRoot; 31 | @property (nonatomic, weak) LMNLineChain *lineChain; 32 | 33 | @property (nonatomic, copy, readwrite) NSString *uuid; 34 | @property (nonatomic, weak, readwrite) LMNLine *prev; 35 | 36 | @end 37 | 38 | @implementation LMNLine 39 | 40 | static CGFloat kLineSpacing = 2.f; 41 | 42 | - (instancetype)init 43 | { 44 | self = [super init]; 45 | if (self) { 46 | _uuid = [NSUUID UUID].UUIDString; 47 | } 48 | return self; 49 | } 50 | 51 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 52 | { 53 | self = [super init]; 54 | if (self) { 55 | self.uuid = [aDecoder decodeObjectForKey:@"uuid"]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)encodeWithCoder:(NSCoder *)aCoder 61 | { 62 | [aCoder encodeObject:self.uuid forKey:@"uuid"]; 63 | } 64 | 65 | #pragma mark - getter & setter 66 | 67 | - (void)setNext:(LMNLine *)next 68 | { 69 | _next = next; 70 | next.prev = self; 71 | } 72 | 73 | - (void)makeRootOfLineChain:(LMNLineChain *)lineChain 74 | { 75 | self.isRoot = YES; 76 | self.lineChain = lineChain; 77 | lineChain.rootLine = self; 78 | } 79 | 80 | #pragma mark - attributes for paragraph modes 81 | 82 | - (NSMutableDictionary *)attributesWithFont:(UIFont *)font 83 | { 84 | static UIFont *defaultFont; 85 | static dispatch_once_t onceToken; 86 | dispatch_once(&onceToken, ^{ 87 | defaultFont = [UIFont fontWithFontSize:17.f bold:NO italic:NO]; 88 | }); 89 | if (!font) { 90 | font = defaultFont; 91 | } 92 | 93 | NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 94 | attributes[NSForegroundColorAttributeName] = [UIColor grayColor]; 95 | attributes[NSFontAttributeName] = font; 96 | 97 | NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 98 | paragraphStyle.lineSpacing = kLineSpacing; 99 | attributes[NSParagraphStyleAttributeName] = paragraphStyle; 100 | return attributes; 101 | } 102 | 103 | - (NSDictionary *)attributes 104 | { 105 | return nil; 106 | } 107 | 108 | - (void)insteadOfLine:(LMNLine *)line 109 | { 110 | self.range = line.range; 111 | self.next = line.next; 112 | line.prev.next = self; 113 | if (line.isRoot) { 114 | [self makeRootOfLineChain:line.lineChain]; 115 | } 116 | [line clean]; 117 | } 118 | 119 | - (void)inheritFromLine:(LMNLine *)line {} 120 | 121 | - (void)clean {} 122 | 123 | - (void)dealloc { 124 | [self clean]; 125 | } 126 | 127 | @end 128 | 129 | @implementation LMNLine (Mode) 130 | 131 | #pragma mark - mode 132 | 133 | + (instancetype)line 134 | { 135 | return [self lineWithMode:LMNLineModeContent]; 136 | } 137 | 138 | + (instancetype)lineWithMode:(LMNLineMode)mode 139 | { 140 | return [lmn_lineClassOf(mode) new]; 141 | } 142 | 143 | - (BOOL)isKindOfMode:(LMNLineMode)mode 144 | { 145 | Class cls = lmn_lineClassOf(mode); 146 | return [self isKindOfClass:cls]; 147 | } 148 | 149 | - (LMNLineMode)mode 150 | { 151 | NSArray *modes = @[ 152 | LMNLineModeContent, 153 | LMNLineModeTitle, 154 | LMNLineModeSubTitle, 155 | LMNLineModeNumbering, 156 | LMNLineModeBullets, 157 | LMNLineModeCheckbox, 158 | LMNLineModeModeImage, 159 | ]; 160 | for (LMNLineMode mode in modes) { 161 | if ([self isKindOfMode:mode]) { 162 | return mode; 163 | } 164 | } 165 | return LMNLineModeModeUnknown; 166 | } 167 | 168 | @end 169 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLineChain+Numbering.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNLineChain+Numbering.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLineChain.h" 10 | 11 | @interface LMNLineChain (Numbering) 12 | 13 | - (void)updateNumberings; 14 | - (void)updateNumberingStartWithLine:(LMNLine *)line; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLineChain+Numbering.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNLineChain+Numbering.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLineChain+Numbering.h" 10 | #import "LMNNumberingLine.h" 11 | #import "LMNNumberingLine.h" 12 | 13 | @implementation LMNLineChain (Numbering) 14 | 15 | - (void)updateNumberings 16 | { 17 | // 将连续数字段落的第一个节点提取出来。 18 | id headLine = nil; 19 | NSMutableArray *numberingLines = [NSMutableArray array]; 20 | LMNLine *line = self.rootLine; 21 | do { 22 | BOOL isNumbering = [line isKindOfClass:[LMNNumberingLine class]]; 23 | if (isNumbering && headLine == nil) { 24 | headLine = line; 25 | } 26 | else if (!isNumbering && headLine != nil) { 27 | [numberingLines addObject:headLine]; 28 | headLine = nil; 29 | } 30 | line = line.next; 31 | } 32 | while (line); 33 | 34 | if (headLine) { 35 | [numberingLines addObject:headLine]; 36 | headLine = nil; 37 | } 38 | for (LMNLine *line in numberingLines) { 39 | [self updateNumberingStartWithLine:line]; 40 | } 41 | } 42 | 43 | - (void)updateNumberingStartWithLine:(LMNLine *)line 44 | { 45 | if (![line isKindOfClass:[LMNNumberingLine class]]) { 46 | return; 47 | } 48 | NSUInteger offset = 0; 49 | if ([line.prev isKindOfClass:[LMNNumberingLine class]]) { 50 | offset += ((LMNNumberingLine *)line.prev).number; 51 | } 52 | NSMutableArray *lines = [NSMutableArray array]; 53 | while (line && [line isKindOfClass:[LMNNumberingLine class]]) { 54 | [lines addObject:line]; 55 | line = line.next; 56 | } 57 | [lines enumerateObjectsUsingBlock:^(LMNLine *obj, NSUInteger idx, BOOL *stop) { 58 | [(LMNNumberingLine *)obj setNumber:idx + 1 + offset]; 59 | }]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLineChain.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNLineChain.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/3/17. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LMNLine.h" 11 | 12 | @class LMNImageView; 13 | 14 | @interface LMNLineChain : NSObject 15 | 16 | @property (nonatomic, readonly) NSString *text; 17 | - (void)updateWithText:(NSString *)text; 18 | 19 | @property (nonatomic, strong) LMNLine *rootLine; 20 | - (LMNLine *)lineAtLocation:(NSUInteger)loc; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLineChain.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNLineChain.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/3/17. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLineChain.h" 10 | #import "LMNImageView.h" 11 | 12 | @interface LMNLineChain () 13 | 14 | @property (nonatomic, copy) NSString *text; 15 | 16 | @end 17 | 18 | @implementation LMNLineChain 19 | 20 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 21 | { 22 | self = [super init]; 23 | if (self) { 24 | NSArray *lines = [aDecoder decodeObjectForKey:@"lines"]; 25 | if (lines.count == 0) { 26 | return nil; 27 | } 28 | LMNLine *tail = nil; 29 | for (LMNLine *line in lines) { 30 | if (tail) { 31 | tail.next = line; 32 | } 33 | else { 34 | [line makeRootOfLineChain:self]; 35 | } 36 | tail = line; 37 | } 38 | NSString *text = [aDecoder decodeObjectForKey:@"text"]; 39 | [self updateWithText:text]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)encodeWithCoder:(NSCoder *)aCoder 45 | { 46 | NSMutableArray *lines = [NSMutableArray array]; 47 | LMNLine *line = self.rootLine; 48 | do { 49 | [lines addObject:line]; 50 | } while ((line = line.next)); 51 | 52 | [aCoder encodeObject:lines forKey:@"lines"]; 53 | [aCoder encodeObject:self.text forKey:@"text"]; 54 | } 55 | 56 | - (instancetype)init 57 | { 58 | self = [super init]; 59 | if (self) { 60 | LMNLine *line = [LMNLine lineWithMode:LMNLineModeContent]; 61 | [line makeRootOfLineChain:self]; 62 | } 63 | return self; 64 | } 65 | 66 | - (void)updateWithText:(NSString *)text 67 | { 68 | _text = [text copy]; 69 | 70 | NSRange range = NSMakeRange(0, 0); 71 | LMNLine *line = self.rootLine; 72 | while (YES) { 73 | range = [text paragraphRangeForRange:range]; 74 | line.range = range; 75 | range.location = NSMaxRange(range); 76 | range.length = 0; 77 | if (line.next) { 78 | line = line.next; 79 | if (range.location >= text.length) { 80 | line.range = range; 81 | break; 82 | } 83 | } 84 | else { 85 | break; 86 | } 87 | } 88 | } 89 | 90 | - (LMNLine *)lineAtLocation:(NSUInteger)loc 91 | { 92 | LMNLine *line = self.rootLine; 93 | while (line) { 94 | if (NSLocationInRange(loc, line.range)) { 95 | return line; 96 | } 97 | if (line.next == nil && self.text.length == loc) { 98 | return line; 99 | } 100 | line = line.next; 101 | } 102 | return nil; 103 | } 104 | 105 | @end 106 | 107 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNLineModes.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNLineModes.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #ifndef LMNLineModes_h 10 | #define LMNLineModes_h 11 | 12 | typedef NSString * LMNLineMode; 13 | 14 | extern LMNLineMode const LMNLineModeContent; 15 | extern LMNLineMode const LMNLineModeTitle; 16 | extern LMNLineMode const LMNLineModeSubTitle; 17 | extern LMNLineMode const LMNLineModeNumbering; 18 | extern LMNLineMode const LMNLineModeBullets; 19 | extern LMNLineMode const LMNLineModeCheckbox; 20 | extern LMNLineMode const LMNLineModeModeImage; 21 | extern LMNLineMode const LMNLineModeModeUnknown; 22 | 23 | #endif /* LMNLineModes_h */ 24 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNNumberingLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNNumberingLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNSpecialLine.h" 10 | 11 | @interface LMNNumberingLine : LMNSpecialLine 12 | 13 | @property (nonatomic, assign) NSUInteger number; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNNumberingLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNNumberingLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNNumberingLine.h" 10 | #import "UIFont+LMNote.h" 11 | 12 | @interface LMNNumberingLine () 13 | 14 | @property (nonatomic, strong) UILabel *numberLabel; 15 | 16 | @end 17 | 18 | @implementation LMNNumberingLine 19 | 20 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 21 | { 22 | self = [super initWithCoder:aDecoder]; 23 | if (self) { 24 | self.number = [aDecoder decodeIntegerForKey:@"number"]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)encodeWithCoder:(NSCoder *)aCoder 30 | { 31 | [super encodeWithCoder:aCoder]; 32 | [aCoder encodeInteger:self.number forKey:@"number"]; 33 | } 34 | 35 | #pragma mark - number 36 | 37 | - (void)setNumber:(NSUInteger)number 38 | { 39 | _number = number; 40 | self.numberLabel.text = [@(number).stringValue stringByAppendingString:@" ."]; 41 | self.numberLabel.attributedText = ({ 42 | NSMutableAttributedString *attributedText = [self.numberLabel.attributedText mutableCopy]; 43 | [attributedText addAttribute:NSBaselineOffsetAttributeName value:@(-0.5f) range:NSMakeRange(0, attributedText.length)]; 44 | attributedText; 45 | }); 46 | } 47 | 48 | #pragma - left view 49 | 50 | - (CGSize)intrinsicLeftSize 51 | { 52 | CGFloat width = LMNSpecialLineHeight; 53 | if (self.numberLabel) { 54 | [self.numberLabel sizeToFit]; 55 | width = MAX(CGRectGetWidth(self.numberLabel.frame), LMNSpecialLineHeight); 56 | } 57 | return CGSizeMake(width, CGRectGetHeight(self.numberLabel.frame)); 58 | } 59 | 60 | - (void)loadLeftView 61 | { 62 | if (self.numberLabel) { 63 | return; 64 | } 65 | self.numberLabel = ({ 66 | UILabel *label = [[UILabel alloc] init]; 67 | label.font = [UIFont fontWithFontSize:17.f bold:NO italic:NO]; 68 | label.textAlignment = NSTextAlignmentRight; 69 | label.textColor = [UIColor grayColor]; 70 | label; 71 | }); 72 | self.number = self.number; 73 | } 74 | 75 | - (UIView *)leftView 76 | { 77 | return self.numberLabel; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNSpecialLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNSpecialLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLine.h" 10 | 11 | extern CGFloat const LMNSpecialLineHeight; 12 | 13 | @interface LMNSpecialLine : LMNLine 14 | 15 | @property (nonatomic, readonly) UIView *leftView; 16 | - (CGSize)intrinsicLeftSize; 17 | - (void)loadLeftView; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNSpecialLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNSpecialLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNSpecialLine.h" 10 | 11 | @implementation LMNSpecialLine 12 | 13 | CGFloat const LMNSpecialLineHeight = 26.f; 14 | 15 | - (NSDictionary *)attributes 16 | { 17 | static NSDictionary *attributes; 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | attributes = [self attributesWithFont:nil]; 21 | UIFont *font = attributes[NSFontAttributeName]; 22 | CGFloat paragraphSpacing = (LMNSpecialLineHeight - roundf(font.lineHeight)) / 2.f; 23 | NSMutableParagraphStyle *paragraphStyle = attributes[NSParagraphStyleAttributeName]; 24 | paragraphStyle.paragraphSpacing = paragraphSpacing; 25 | paragraphStyle.paragraphSpacingBefore = paragraphSpacing; 26 | attributes = [attributes copy]; 27 | }); 28 | return attributes; 29 | } 30 | 31 | - (CGSize)intrinsicLeftSize 32 | { 33 | return CGSizeMake(LMNSpecialLineHeight, LMNSpecialLineHeight); 34 | } 35 | 36 | - (void)loadLeftView {} 37 | 38 | - (void)clean 39 | { 40 | [self.leftView removeFromSuperview]; 41 | [super clean]; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNTextLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextLine.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNLine.h" 10 | 11 | @interface LMNTextLine : LMNLine 12 | 13 | @property (nonatomic, assign) NSTextAlignment textAlignment; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNote/LMNote/Lines/LMNTextLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextLine.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/9. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNTextLine.h" 10 | #import "UIFont+LMNote.h" 11 | 12 | @interface LMNTextLine () 13 | 14 | @property (nonatomic, readonly) UIFont *textFont; 15 | 16 | @end 17 | 18 | @implementation LMNTextLine 19 | 20 | - (UIFont *)textFont 21 | { 22 | return nil; 23 | } 24 | 25 | - (NSDictionary *)attributes 26 | { 27 | NSMutableDictionary *attributes = [self attributesWithFont:self.textFont]; 28 | NSMutableParagraphStyle *paragraphStyle = attributes[NSParagraphStyleAttributeName]; 29 | paragraphStyle.alignment = self.textAlignment; 30 | return attributes; 31 | } 32 | 33 | - (void)inheritFromLine:(LMNLine *)line 34 | { 35 | [super inheritFromLine:line]; 36 | if ([line isKindOfClass:[LMNTextLine class]]) { 37 | self.textAlignment = ((LMNTextLine *)line).textAlignment; 38 | } 39 | } 40 | 41 | @end 42 | 43 | #pragma mark - 44 | 45 | @interface LMNContentLine : LMNTextLine 46 | @end 47 | 48 | @implementation LMNContentLine 49 | 50 | - (UIFont *)textFont 51 | { 52 | return [UIFont fontWithFontSize:17.f bold:NO italic:NO]; 53 | } 54 | 55 | @end 56 | 57 | #pragma mark - 58 | 59 | @interface LMNTitleLine : LMNTextLine 60 | @end 61 | 62 | @implementation LMNTitleLine 63 | 64 | - (UIFont *)textFont 65 | { 66 | return [UIFont fontWithFontSize:24.f bold:YES italic:NO]; 67 | } 68 | 69 | @end 70 | 71 | #pragma mark - 72 | 73 | @interface LMNSubtitleLine : LMNTextLine 74 | @end 75 | 76 | @implementation LMNSubtitleLine 77 | 78 | - (UIFont *)textFont 79 | { 80 | return [UIFont fontWithFontSize:20.f bold:YES italic:NO]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNImageInputViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNImageInputViewController.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/4/19. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | @import Photos; 11 | 12 | @class LMNImageInputViewController; 13 | 14 | @protocol LMNImageInputViewControllerDelegate 15 | 16 | - (void)lmn_imageInput:(LMNImageInputViewController *)viewController didSelectPHAsset:(PHAsset *)asset; 17 | - (void)lmn_imageInputClose:(LMNImageInputViewController *)viewController; 18 | 19 | @end 20 | 21 | @interface LMNImageInputViewController : UIViewController 22 | 23 | @property (nonatomic, weak) id delegate; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNImageInputViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNImageInputViewController.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/4/19. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNImageInputViewController.h" 10 | #import "LMNPhotoCollectionCell.h" 11 | 12 | @import Photos; 13 | 14 | @interface LMNImageInputViewController () 15 | 16 | @property (nonatomic, strong) UIView *headerView; 17 | @property (nonatomic, strong) UIImageView *headerImageView; 18 | @property (nonatomic, strong) UILabel *headerLabel; 19 | @property (nonatomic, strong) UIButton *closeButton; 20 | @property (nonatomic, strong) UICollectionView *collectionView; 21 | 22 | @property (nonatomic, strong) PHFetchResult *photosResult; 23 | @property (nonatomic, strong) NSIndexPath *selectedIndexPath; 24 | 25 | @end 26 | 27 | @implementation LMNImageInputViewController 28 | 29 | static NSString * kReuseIdentifier = @"photo"; 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | self.view.backgroundColor = [UIColor clearColor]; 34 | 35 | [self loadSubviews]; 36 | [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; 37 | } 38 | 39 | - (void)loadSubviews 40 | { 41 | self.headerImageView = ({ 42 | UIImageView *view = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"lmn_tool_image"]]; 43 | view.contentMode = UIViewContentModeCenter; 44 | view; 45 | }); 46 | self.headerLabel = ({ 47 | UILabel *label = [[UILabel alloc] init]; 48 | label.textColor = [UIColor colorWithWhite:0.1 alpha:1.f]; 49 | label.font = [UIFont systemFontOfSize:15.f]; 50 | label.text = @"照片"; 51 | label; 52 | }); 53 | self.closeButton = ({ 54 | UIImage *image = [UIImage imageNamed:@"lmn_tool_close"]; 55 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 56 | [button setImage:image forState:UIControlStateNormal]; 57 | button; 58 | }); 59 | [self.closeButton addTarget:self action:@selector(close:) forControlEvents:UIControlEventTouchUpInside]; 60 | 61 | self.headerView = ({ 62 | UIView *view = [[UIView alloc] init]; 63 | view.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.f]; 64 | view; 65 | }); 66 | [self.headerView addSubview:self.headerImageView]; 67 | [self.headerView addSubview:self.headerLabel]; 68 | [self.headerView addSubview:self.closeButton]; 69 | [self.view addSubview:self.headerView]; 70 | 71 | self.collectionView = ({ 72 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 73 | UICollectionView *view = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; 74 | view.backgroundColor = self.headerView.backgroundColor; 75 | view.dataSource = self; 76 | view.delegate = self; 77 | view; 78 | }); 79 | [self.view addSubview:self.collectionView]; 80 | 81 | [self.collectionView registerClass:[LMNPhotoCollectionCell class] forCellWithReuseIdentifier:kReuseIdentifier]; 82 | } 83 | 84 | - (void)viewDidLayoutSubviews { 85 | [super viewDidLayoutSubviews]; 86 | 87 | CGRect rect = self.view.bounds; 88 | rect.size.height = 44.f; 89 | self.headerView.frame = rect; 90 | 91 | self.headerImageView.frame = CGRectMake(0, 0, 44.f, 44.f); 92 | self.headerLabel.frame = CGRectMake(44, 0, 100.f, 44.f); 93 | self.closeButton.frame = CGRectMake(CGRectGetWidth(self.headerView.bounds) - 52.f, 0, 44.f, 44.f); 94 | 95 | static CGFloat kSpacing = 2.f; 96 | UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; 97 | layout.minimumLineSpacing = kSpacing; 98 | layout.minimumInteritemSpacing = kSpacing; 99 | CGFloat width = CGRectGetWidth(self.view.bounds) / 3.f - kSpacing; 100 | layout.itemSize = CGSizeMake(width, width); 101 | 102 | rect.origin.y = CGRectGetHeight(self.headerView.frame); 103 | rect.size.height = CGRectGetHeight(self.view.bounds) - rect.origin.y; 104 | self.collectionView.frame = rect; 105 | } 106 | 107 | - (void)viewWillAppear:(BOOL)animated 108 | { 109 | [super viewWillAppear:animated]; 110 | 111 | if (self.photosResult.count == 0) { 112 | [self fetchPhotos]; 113 | } 114 | else { 115 | [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO]; 116 | } 117 | } 118 | 119 | - (void)viewDidDisappear:(BOOL)animated 120 | { 121 | [super viewDidDisappear:animated]; 122 | if (self.selectedIndexPath) { 123 | [self.collectionView deselectItemAtIndexPath:self.selectedIndexPath animated:NO]; 124 | self.selectedIndexPath = nil; 125 | } 126 | } 127 | 128 | - (void)dealloc { 129 | [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self]; 130 | } 131 | 132 | - (void)fetchPhotos { 133 | PHFetchOptions *nearestPhotosOptions = [[PHFetchOptions alloc] init]; 134 | nearestPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; 135 | self.photosResult = [PHAsset fetchAssetsWithOptions:nearestPhotosOptions]; 136 | 137 | dispatch_async(dispatch_get_main_queue(), ^{ 138 | [self.collectionView reloadData]; 139 | }); 140 | } 141 | 142 | - (void)reload { 143 | self.selectedIndexPath = nil; 144 | [self.collectionView reloadData]; 145 | } 146 | 147 | - (void)close:(id)sender 148 | { 149 | if ([self.delegate respondsToSelector:@selector(lmn_imageInputClose:)]) { 150 | [self.delegate lmn_imageInputClose:self]; 151 | } 152 | } 153 | 154 | #pragma mark - 155 | 156 | - (void)photoLibraryDidChange:(PHChange *)changeInstance { 157 | [self fetchPhotos]; 158 | } 159 | 160 | #pragma mark - 161 | 162 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 163 | return self.photosResult.count; 164 | } 165 | 166 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 167 | 168 | LMNPhotoCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; 169 | PHAsset *asset = self.photosResult[indexPath.item]; 170 | [cell setAsset:asset]; 171 | if (!cell.handler) { 172 | cell.handler = ^(PHAsset *asset) { 173 | [self.delegate lmn_imageInput:self didSelectPHAsset:asset]; 174 | }; 175 | } 176 | return cell; 177 | } 178 | 179 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 180 | { 181 | if (self.selectedIndexPath == indexPath) { 182 | return; 183 | } 184 | if (self.selectedIndexPath) { 185 | UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:self.selectedIndexPath]; 186 | [(LMNPhotoCollectionCell *)cell performSelectionAnimations]; 187 | } 188 | UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; 189 | [(LMNPhotoCollectionCell *)cell performSelectionAnimations]; 190 | self.selectedIndexPath = indexPath; 191 | } 192 | 193 | #pragma mark - 194 | 195 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 196 | 197 | [picker dismissViewControllerAnimated:YES completion:nil]; 198 | 199 | UIImage *originalImage = info[UIImagePickerControllerOriginalImage]; 200 | CGSize targetSize = [UIScreen mainScreen].bounds.size; 201 | targetSize.width *= 2; 202 | targetSize.height = targetSize.width * originalImage.size.height / originalImage.size.width; 203 | 204 | UIGraphicsBeginImageContext(targetSize); 205 | [originalImage drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)]; 206 | __unused UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); 207 | UIGraphicsEndImageContext(); 208 | } 209 | 210 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 211 | [picker dismissViewControllerAnimated:YES completion:nil]; 212 | } 213 | 214 | @end 215 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNImageView.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/4/24. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class LMNImageView; 12 | @class LMNImageLine; 13 | 14 | @protocol LMNImageViewDelegate 15 | 16 | @optional 17 | - (void)lmn_imageViewBeginEditing:(LMNImageView *)imageView; 18 | - (void)lmn_imageViewEndEditing:(LMNImageView *)imageView; 19 | - (void)lmn_imageViewDelete:(LMNImageView *)imageView; 20 | 21 | @end 22 | 23 | @interface LMNImageView : UIView 24 | 25 | @property (nonatomic, weak) id delegate; 26 | 27 | @property (nonatomic, strong) UIImage *image; 28 | @property (nonatomic, strong) UIBezierPath *exclusionPath; 29 | 30 | @property (nonatomic, readonly) BOOL editing; 31 | 32 | - (instancetype)initWithImage:(UIImage *)image; 33 | 34 | - (void)beginEditing; 35 | - (void)endEditing; 36 | 37 | @property (nonatomic, weak) LMNImageLine *owner; 38 | - (void)unbindFromOwner; 39 | 40 | + (CGSize)sizeThatFit:(UIImage *)image limitWidth:(CGFloat)limitWidth; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNImageView.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/4/24. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNImageView.h" 10 | #import "LMNImageLine.h" 11 | 12 | @interface LMNImageView () 13 | 14 | @property (nonatomic, strong) UIControl *borderView; 15 | @property (nonatomic, strong) UIImageView *imageView; 16 | @property (nonatomic, strong) UIButton *deleteButton; 17 | 18 | @property (nonatomic, strong) UIVisualEffectView *blurEffectView; 19 | @property (nonatomic, assign) BOOL editing; 20 | 21 | @end 22 | 23 | @implementation LMNImageView 24 | 25 | static CGFloat const kMargin = 5.f; 26 | static CGFloat const kVerticalInset = 5.f; 27 | static CGFloat const kVerticalMargin = kVerticalInset + kMargin; 28 | 29 | + (CGSize)sizeThatFit:(UIImage *)image limitWidth:(CGFloat)limitWidth 30 | { 31 | if (!image) { 32 | return CGSizeZero; 33 | } 34 | CGFloat height = (limitWidth - kMargin * 2) * image.size.height / image.size.width + kVerticalMargin * 2; 35 | return CGSizeMake(limitWidth, roundf(height)); 36 | } 37 | 38 | - (instancetype)initWithImage:(UIImage *)image 39 | { 40 | self = [self initWithFrame:CGRectZero]; 41 | if (self) { 42 | self.backgroundColor = [UIColor whiteColor]; 43 | 44 | _borderView = [[UIControl alloc] init]; 45 | _borderView.layer.borderColor = [UIColor colorWithWhite:0.8f alpha:1.f].CGColor; 46 | _borderView.layer.borderWidth = 0.5f; 47 | _borderView.layer.cornerRadius = 2.f; 48 | [self addSubview:_borderView]; 49 | 50 | _image = image; 51 | _imageView = [[UIImageView alloc] initWithImage:image]; 52 | _imageView.clipsToBounds = YES; 53 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 54 | [_borderView addSubview:_imageView]; 55 | 56 | _deleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; 57 | [_deleteButton setImage:[UIImage imageNamed:@"lmn_delete"] forState:UIControlStateNormal]; 58 | _deleteButton.hidden = YES; 59 | [self addSubview:_deleteButton]; 60 | 61 | [_borderView addTarget:self action:@selector(tapContainer:) forControlEvents:UIControlEventTouchUpInside]; 62 | [_deleteButton addTarget:self action:@selector(delete:) forControlEvents:UIControlEventTouchUpInside]; 63 | } 64 | return self; 65 | } 66 | 67 | - (void)layoutSubviews 68 | { 69 | [super layoutSubviews]; 70 | self.borderView.frame = CGRectInset(self.bounds, 0, kVerticalInset); 71 | self.imageView.frame = CGRectInset(self.borderView.bounds, kMargin, kMargin); 72 | self.deleteButton.frame = CGRectMake(kMargin + 5.f, kVerticalMargin + 5.f, 34.f, 34.f); 73 | } 74 | 75 | - (void)setImage:(UIImage *)image 76 | { 77 | _image = image; 78 | self.imageView.image = image; 79 | [self setNeedsDisplay]; 80 | } 81 | 82 | #pragma mark - editing 83 | 84 | - (void)setEditing:(BOOL)editing 85 | { 86 | if (_editing == editing) { 87 | return; 88 | } 89 | _editing = editing; 90 | 91 | if (!self.blurEffectView) { 92 | if (!UIAccessibilityIsReduceTransparencyEnabled()) { 93 | UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 94 | UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 95 | blurEffectView.userInteractionEnabled = NO; 96 | _blurEffectView = blurEffectView; 97 | } 98 | } 99 | if (!self.blurEffectView) { 100 | self.deleteButton.hidden = !editing; 101 | return; 102 | } 103 | 104 | self.deleteButton.hidden = !editing; 105 | if (editing) { 106 | self.blurEffectView.frame = self.imageView.frame; 107 | [self.borderView addSubview:self.blurEffectView]; 108 | 109 | self.deleteButton.alpha = 0; 110 | self.blurEffectView.alpha = 0; 111 | [UIView animateWithDuration:0.25 animations:^{ 112 | self.blurEffectView.alpha = 1; 113 | self.deleteButton.alpha = 1; 114 | }]; 115 | } 116 | else { 117 | [UIView animateWithDuration:0.25 animations:^{ 118 | self.blurEffectView.alpha = 0; 119 | self.deleteButton.alpha = 0; 120 | } completion:^(BOOL finished) { 121 | [self.blurEffectView removeFromSuperview]; 122 | }]; 123 | } 124 | } 125 | 126 | - (void)beginEditing 127 | { 128 | self.editing = YES; 129 | if ([self.delegate respondsToSelector:@selector(lmn_imageViewBeginEditing:)]) { 130 | [self.delegate lmn_imageViewBeginEditing:self]; 131 | } 132 | } 133 | 134 | - (void)endEditing 135 | { 136 | self.editing = NO; 137 | if ([self.delegate respondsToSelector:@selector(lmn_imageViewEndEditing:)]) { 138 | [self.delegate lmn_imageViewEndEditing:self]; 139 | } 140 | } 141 | 142 | #pragma mark - actions 143 | 144 | - (void)tapContainer:(id)sender 145 | { 146 | if (self.editing) { 147 | [self endEditing]; 148 | } 149 | else { 150 | [self beginEditing]; 151 | } 152 | } 153 | 154 | - (void)delete:(id)sender 155 | { 156 | if ([self.delegate respondsToSelector:@selector(lmn_imageViewDelete:)]) { 157 | [self.delegate lmn_imageViewDelete:self]; 158 | } 159 | } 160 | 161 | #pragma mark - 162 | 163 | - (void)unbindFromOwner 164 | { 165 | if (self.owner.bindingImageView == self) { 166 | [self.owner unbindImageView]; 167 | } 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNPhotoCollectionCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMPhotoCollectionCell.h 3 | // SimpleWord 4 | // 5 | // Created by littleMeaning on 16/5/16. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class PHAsset; 12 | 13 | @interface LMNPhotoCollectionCell : UICollectionViewCell 14 | 15 | @property (nonatomic, copy) void (^handler)(PHAsset *asset); 16 | 17 | - (void)setAsset:(PHAsset *)asset; 18 | - (void)performSelectionAnimations; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNPhotoCollectionCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMPhotoCollectionCell.m 3 | // SimpleWord 4 | // 5 | // Created by littleMeaning on 16/5/16. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "LMNPhotoCollectionCell.h" 10 | 11 | @import Photos; 12 | 13 | @interface LMNPhotoCollectionCell () 14 | 15 | @property (nonatomic, strong) PHAsset *asset; 16 | @property (nonatomic, strong) UIImageView *imageView; 17 | @property (nonatomic, strong) UIView *bezelView; 18 | @property (nonatomic, strong) UIButton *useButton; 19 | 20 | @end 21 | 22 | @implementation LMNPhotoCollectionCell 23 | 24 | - (instancetype)initWithFrame:(CGRect)frame 25 | { 26 | if (self = [super initWithFrame:frame]) { 27 | self.clipsToBounds = YES; 28 | 29 | _imageView = [[UIImageView alloc] init]; 30 | _imageView.contentMode = UIViewContentModeScaleAspectFill; 31 | _imageView.clipsToBounds = YES; 32 | [self.contentView addSubview:_imageView]; 33 | 34 | _bezelView = [[UIView alloc] init]; 35 | _bezelView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3]; 36 | _bezelView.layer.borderColor = [UIColor colorWithWhite:0.98 alpha:1.f].CGColor; 37 | _bezelView.layer.borderWidth = 2.f; 38 | _bezelView.hidden = YES; 39 | [self.contentView addSubview:_bezelView]; 40 | 41 | _useButton = [UIButton buttonWithType:UIButtonTypeCustom]; 42 | _useButton.titleLabel.font = [UIFont systemFontOfSize:15.f]; 43 | _useButton.layer.borderColor = [UIColor whiteColor].CGColor; 44 | _useButton.layer.borderWidth = 1.f; 45 | _useButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3f]; 46 | [_useButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 47 | [_useButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateHighlighted]; 48 | [_useButton setTitle:@"使用" forState:UIControlStateNormal]; 49 | [_bezelView addSubview:_useButton]; 50 | 51 | [_useButton addTarget:self action:@selector(useAction:) forControlEvents:UIControlEventTouchUpInside]; 52 | } 53 | return self; 54 | } 55 | 56 | - (void)layoutSubviews 57 | { 58 | [super layoutSubviews]; 59 | self.imageView.frame = self.bounds; 60 | self.bezelView.frame = self.bounds; 61 | CGRect rect = CGRectMake(0, 0, 60.f, 36.f); 62 | rect.origin.x = (CGRectGetWidth(self.bounds) - CGRectGetWidth(rect)) / 2.f; 63 | rect.origin.y = (CGRectGetHeight(self.bounds) - CGRectGetHeight(rect)) / 2.f; 64 | self.useButton.frame = rect; 65 | self.useButton.layer.cornerRadius = CGRectGetHeight(rect) / 2.f; 66 | } 67 | 68 | - (void)setSelected:(BOOL)selected 69 | { 70 | // TODO: 在滑动的时候会触发,导致闪烁。 71 | [super setSelected:selected]; 72 | self.bezelView.hidden = !selected; 73 | } 74 | 75 | - (void)performSelectionAnimations 76 | { 77 | CGFloat scale = self.selected ? 1.1f : 1.f; 78 | self.bezelView.alpha = !self.selected; 79 | self.bezelView.hidden = NO; 80 | [UIView animateWithDuration:0.25 animations:^{ 81 | self.bezelView.alpha = self.selected; 82 | self.imageView.transform = CGAffineTransformMakeScale(scale, scale); 83 | } completion:^(BOOL finished) { 84 | if (!self.selected) { 85 | self.bezelView.hidden = YES; 86 | } 87 | }]; 88 | } 89 | 90 | - (void)setAsset:(PHAsset *)asset { 91 | 92 | _asset = asset; 93 | if (!asset) { 94 | return; 95 | } 96 | CGFloat imageWidth = CGRectGetWidth([UIScreen mainScreen].bounds) / 3.f; 97 | CGSize targetSize = CGSizeMake(imageWidth, imageWidth); 98 | [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFit options:nil resultHandler:^(UIImage *result, NSDictionary *info) { 99 | if (self.asset == asset) { 100 | self.imageView.image = result; 101 | } 102 | }]; 103 | } 104 | 105 | - (void)prepareForReuse 106 | { 107 | self.imageView.image = nil; 108 | self.asset = nil; 109 | self.bezelView.hidden = YES; 110 | } 111 | 112 | - (void)useAction:(UIButton *)sender 113 | { 114 | self.handler(self.asset); 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNToolBar.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/4/17. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LMNLineModes.h" 11 | 12 | extern NSString * const LMFontBoldAttributeName; 13 | extern NSString * const LMFontUnderlineAttributeName; 14 | extern NSString * const LMFontItalicAttributeName; 15 | extern NSString * const LMFontStrikethroughAttributeName; 16 | extern NSString * const LMLineModeAttributeName; 17 | 18 | typedef NS_ENUM(NSUInteger, LMNToolBarItemTag) { 19 | LMNToolBarItemTagImage = 1001, 20 | }; 21 | 22 | @class LMNToolBar; 23 | 24 | extern NSInteger const LMNToolBarIndexOfCloseItem; 25 | 26 | @protocol LMNToolBarDelegate 27 | 28 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedMode:(LMNLineMode)mode; 29 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedAttributes:(NSDictionary *)attributes; 30 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didChangedTextAlignment:(NSTextAlignment)alignment; 31 | - (void)lmn_toolBar:(LMNToolBar *)toolBar didSelectedItemWithTag:(LMNToolBarItemTag)tag; 32 | - (void)lmn_toolBarClose:(LMNToolBar *)toolBar; 33 | 34 | @end 35 | 36 | @interface LMNToolBar : UIView 37 | 38 | @property (nonatomic, weak) id delegate; 39 | @property (nonatomic, copy) LMNLineMode mode; 40 | 41 | + (instancetype)toolBar; 42 | 43 | - (void)showSubToolBar:(BOOL)animated; 44 | - (void)hideSubToolBar:(BOOL)animated; 45 | 46 | - (void)reloadDataWithTypingAttributes:(NSDictionary *)typingAttributes mode:(LMNLineMode)mode isMultiLine:(BOOL)isMultiLine; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNToolBar.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/4/17. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNToolBar.h" 10 | #import "UIFont+LMNote.h" 11 | 12 | NSString * const LMFontBoldAttributeName = @"bold"; 13 | NSString * const LMFontUnderlineAttributeName = @"underline"; 14 | NSString * const LMFontItalicAttributeName = @"italic"; 15 | NSString * const LMFontStrikethroughAttributeName = @"strikethrough"; 16 | NSString * const LMLineModeAttributeName = @"mode"; 17 | 18 | UIImage *lmn_getRectangleImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, CGFloat cornerRadius, CGFloat margin, UIColor *color) 19 | { 20 | UIImage *image; 21 | UIGraphicsBeginImageContextWithOptions(size, opaque, scale); 22 | { 23 | CGRect rect = CGRectZero; 24 | rect.size = size; 25 | rect = CGRectInset(rect, margin, margin); 26 | CAShapeLayer *layer = [CAShapeLayer layer]; 27 | UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius]; 28 | layer.path = path.CGPath; 29 | layer.lineWidth = 0; 30 | layer.fillColor = color.CGColor; 31 | [layer renderInContext:UIGraphicsGetCurrentContext()]; 32 | } 33 | image = UIGraphicsGetImageFromCurrentImageContext(); 34 | UIGraphicsEndImageContext(); 35 | return image; 36 | } 37 | 38 | @interface LMNToolBar () 39 | 40 | @property (nonatomic, strong) UIView *toolBar; 41 | @property (nonatomic, strong) UIView *subToolBar; 42 | @property (nonatomic, strong) UIButton *subToolBarLeftView; 43 | 44 | @property (nonatomic, copy) NSArray *itemButtons; 45 | @property (nonatomic, copy) NSArray *subItemButtons; 46 | 47 | @property (nonatomic, readonly) UIButton *fontButton; 48 | @property (nonatomic, readonly) UIButton *titleButton; 49 | @property (nonatomic, readonly) UIButton *listButton; 50 | @property (nonatomic, readonly) UIButton *checkboxButton; 51 | @property (nonatomic, readonly) UIButton *alignmentButton; 52 | @property (nonatomic, readonly) UIButton *imageButton; 53 | @property (nonatomic, readonly) UIButton *dismissButton; 54 | 55 | @property (nonatomic, readonly) UIButton *boldButton; 56 | @property (nonatomic, readonly) UIButton *italicButton; 57 | @property (nonatomic, readonly) UIButton *underlineButton; 58 | @property (nonatomic, readonly) UIButton *strikethroughButton; 59 | @property (nonatomic, readonly) UIButton *foldButton; 60 | 61 | @property (nonatomic, assign) NSTextAlignment alignment; 62 | 63 | @end 64 | 65 | @implementation LMNToolBar 66 | 67 | - (instancetype)initWithFrame:(CGRect)frame 68 | { 69 | self = [super initWithFrame:frame]; 70 | if (self) { 71 | [self loadSubviews]; 72 | } 73 | return self; 74 | } 75 | 76 | + (instancetype)toolBar 77 | { 78 | return [[self alloc] initWithFrame:CGRectZero]; 79 | } 80 | 81 | - (void)loadToolBar 82 | { 83 | if (!_toolBar) { 84 | _toolBar = [[UIView alloc] init]; 85 | _toolBar.backgroundColor = [UIColor whiteColor]; 86 | NSMutableArray *itemButtons = [NSMutableArray array]; 87 | NSArray *images = @[ 88 | [UIImage imageNamed:@"lmn_tool_a"], 89 | [UIImage imageNamed:@"lmn_tool_t"], 90 | [UIImage imageNamed:@"lmn_list"], 91 | [UIImage imageNamed:@"lmn_list_checkbox"], 92 | [UIImage imageNamed:@"lmn_alignment_left"], 93 | [UIImage imageNamed:@"lmn_tool_image"], 94 | [UIImage imageNamed:@"lmn_tool_close"] 95 | ]; 96 | for (int idx = 0; idx < images.count; idx ++) { 97 | UIImage *image = images[idx]; 98 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 99 | [button setImage:image forState:UIControlStateNormal]; 100 | [_toolBar addSubview:button]; 101 | [itemButtons addObject:button]; 102 | [button addTarget:self 103 | action:@selector(didSelectItem:) 104 | forControlEvents:UIControlEventTouchUpInside]; 105 | 106 | switch (idx) { 107 | case 0: 108 | _fontButton = button; 109 | break; 110 | case 1: 111 | _titleButton = button; 112 | break; 113 | case 2: 114 | _listButton = button; 115 | break; 116 | case 3: 117 | _checkboxButton = button; 118 | break; 119 | case 4: 120 | _alignmentButton = button; 121 | break; 122 | case 5: 123 | button.tag = LMNToolBarItemTagImage; 124 | _imageButton = button; 125 | break; 126 | case 6: 127 | _dismissButton = button; 128 | break; 129 | default: 130 | break; 131 | } 132 | } 133 | self.itemButtons = itemButtons; 134 | } 135 | } 136 | 137 | - (void)loadSubToolBar 138 | { 139 | static UIImage *bgImage; 140 | static UIImage *bgImageSelected; 141 | static dispatch_once_t onceToken; 142 | dispatch_once(&onceToken, ^{ 143 | bgImage = lmn_getRectangleImageWithOptions(CGSizeMake(44.f, 44.f), NO, [UIScreen mainScreen].scale, 4.f, 7.f, [UIColor colorWithWhite:230/255.f alpha:1.f]); 144 | 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]); 145 | }); 146 | 147 | _subToolBar = [[UIView alloc] init]; 148 | _subToolBar.backgroundColor = [UIColor whiteColor]; 149 | NSMutableArray *itemButtons = [NSMutableArray array]; 150 | NSArray *images = @[ 151 | [UIImage imageNamed:@"lmn_font_bold"], 152 | [UIImage imageNamed:@"lmn_font_italic"], 153 | [UIImage imageNamed:@"lmn_font_underline"], 154 | [UIImage imageNamed:@"lmn_font_strikethrough"], 155 | [UIImage imageNamed:@"lmn_left_square"] 156 | ];; 157 | for (int idx = 0; idx < images.count; idx ++) { 158 | UIImage *image = images[idx]; 159 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 160 | [button setImage:image forState:UIControlStateNormal]; 161 | if (idx != 4) { 162 | [button setBackgroundImage:bgImage forState:UIControlStateNormal]; 163 | [button setBackgroundImage:bgImageSelected forState:UIControlStateSelected]; 164 | } 165 | [_subToolBar addSubview:button]; 166 | [itemButtons addObject:button]; 167 | [button addTarget:self 168 | action:@selector(didSelectItem:) 169 | forControlEvents:UIControlEventTouchUpInside]; 170 | 171 | switch (idx) { 172 | case 0: 173 | _boldButton = button; 174 | break; 175 | case 1: 176 | _italicButton = button; 177 | break; 178 | case 2: 179 | _underlineButton = button; 180 | break; 181 | case 3: 182 | _strikethroughButton = button; 183 | break; 184 | case 4: 185 | _foldButton = button; 186 | default: 187 | break; 188 | } 189 | } 190 | self.subItemButtons = itemButtons; 191 | } 192 | 193 | - (void)loadSubviews 194 | { 195 | [self loadToolBar]; 196 | [self loadSubToolBar]; 197 | UIImage *image = [self.fontButton imageForState:UIControlStateNormal]; 198 | self.subToolBarLeftView = [UIButton buttonWithType:UIButtonTypeCustom]; 199 | self.subToolBarLeftView.backgroundColor = [UIColor whiteColor]; 200 | [self.subToolBarLeftView setImage:image forState:UIControlStateNormal]; 201 | self.subToolBarLeftView.hidden = YES; 202 | self.subToolBar.hidden = YES; 203 | [self addSubview:self.toolBar]; 204 | [self addSubview:self.subToolBarLeftView]; 205 | [self addSubview:self.subToolBar]; 206 | [self.subToolBarLeftView addTarget:self action:@selector(didSelectItem:) forControlEvents:UIControlEventTouchUpInside]; 207 | } 208 | 209 | - (void)layoutSubviews 210 | { 211 | [super layoutSubviews]; 212 | { 213 | self.toolBar.frame = self.bounds; 214 | CGRect rect = self.toolBar.bounds; 215 | rect.size.width = CGRectGetHeight(rect); 216 | rect.origin.x = 8.f; 217 | for (UIButton *button in self.itemButtons) { 218 | if (button == self.itemButtons.lastObject) { 219 | rect.origin.x = CGRectGetWidth(self.toolBar.bounds) - CGRectGetWidth(rect) - 8.f; 220 | } 221 | button.frame = rect; 222 | rect.origin.x += CGRectGetWidth(rect) + 4.f; 223 | } 224 | } 225 | CGFloat height = CGRectGetHeight(self.bounds); 226 | self.subToolBarLeftView.frame = CGRectMake(0, 0, height, height); 227 | { 228 | self.subToolBar.frame = ({ 229 | CGRect rect = self.bounds; 230 | rect.size.width -= height; 231 | rect.origin.x = height; 232 | rect; 233 | }); 234 | CGRect rect = self.subToolBar.bounds; 235 | rect.size.width = CGRectGetHeight(rect); 236 | rect.origin.x = 8.f; 237 | for (UIButton *button in self.subItemButtons) { 238 | // 隐藏斜体按钮,"PingFang SC" 字体不支持斜体。 239 | if (button == self.italicButton) { 240 | button.hidden = YES; 241 | continue; 242 | } 243 | if (button == self.subItemButtons.lastObject) { 244 | rect.origin.x = CGRectGetWidth(self.subToolBar.bounds) - CGRectGetWidth(rect) - 8.f; 245 | } 246 | button.frame = rect; 247 | rect.origin.x += CGRectGetWidth(rect) + 4.f; 248 | } 249 | } 250 | } 251 | 252 | - (void)didSelectItem:(UIButton *)itemButton 253 | { 254 | if (itemButton.tag > 0) { 255 | [self.delegate lmn_toolBar:self didSelectedItemWithTag:itemButton.tag]; 256 | } 257 | if (itemButton == self.foldButton || itemButton == self.subToolBarLeftView) { 258 | [self hideSubToolBar:YES]; 259 | return; 260 | } 261 | if (itemButton == self.dismissButton) { 262 | [self.delegate lmn_toolBarClose:self]; 263 | return; 264 | } 265 | if (itemButton == self.alignmentButton) { 266 | self.alignment = (self.alignment + 1) % 3; 267 | [self.delegate lmn_toolBar:self didChangedTextAlignment:self.alignment]; 268 | return; 269 | } 270 | if ([self.subItemButtons containsObject:itemButton]) { 271 | 272 | itemButton.selected = !itemButton.selected; 273 | 274 | BOOL bold = self.boldButton.selected; 275 | BOOL italic = self.italicButton.selected; 276 | BOOL underline = self.underlineButton.selected; 277 | BOOL strikethrough = self.strikethroughButton.selected; 278 | 279 | NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 280 | attributes[LMFontBoldAttributeName] = @(bold); 281 | attributes[LMFontItalicAttributeName] = @(italic); 282 | attributes[LMFontUnderlineAttributeName] = @(underline); 283 | attributes[LMFontStrikethroughAttributeName] = @(strikethrough); 284 | 285 | [self.delegate lmn_toolBar:self didChangedAttributes:attributes]; 286 | return; 287 | } 288 | if ([self.itemButtons containsObject:itemButton]) { 289 | 290 | if (itemButton == self.fontButton) { 291 | [self showSubToolBar:YES]; 292 | } 293 | else if (itemButton == self.titleButton) { 294 | 295 | if ([self.mode isEqualToString:LMNLineModeTitle]) { 296 | [self setMode:LMNLineModeSubTitle]; 297 | } 298 | else if ([self.mode isEqualToString:LMNLineModeSubTitle]) { 299 | [self setMode:LMNLineModeContent]; 300 | } 301 | else { 302 | [self setMode:LMNLineModeTitle]; 303 | } 304 | } 305 | else if (itemButton == self.listButton) { 306 | 307 | if (self.mode == LMNLineModeBullets) { 308 | [self setMode:LMNLineModeNumbering]; 309 | } 310 | else if ([self.mode isEqualToString:LMNLineModeNumbering]) { 311 | [self setMode:LMNLineModeContent]; 312 | } 313 | else { 314 | [self setMode:LMNLineModeBullets]; 315 | } 316 | } 317 | else if (itemButton == self.checkboxButton) { 318 | 319 | if ([self.mode isEqualToString:LMNLineModeCheckbox]) { 320 | [self setMode:LMNLineModeContent]; 321 | } 322 | else { 323 | [self setMode:LMNLineModeCheckbox]; 324 | } 325 | } 326 | [self.delegate lmn_toolBar:self didChangedMode:self.mode]; 327 | return; 328 | } 329 | } 330 | 331 | - (void)showSubToolBar:(BOOL)animated 332 | { 333 | self.subToolBar.alpha = 0; 334 | self.subToolBar.hidden = NO; 335 | self.subToolBarLeftView.frame = [self convertRect:self.fontButton.frame fromView:self.toolBar]; 336 | self.subToolBarLeftView.hidden = NO; 337 | UIButton *foldButton = self.foldButton; 338 | foldButton.alpha = 0; 339 | foldButton.frame = CGRectOffset(foldButton.frame, -CGRectGetWidth(self.subToolBar.frame), 0); 340 | self.fontButton.hidden = YES; 341 | 342 | [UIView animateWithDuration: animated ? 0.35 : 0 animations:^{ 343 | 344 | self.subToolBar.alpha = 1; 345 | self.toolBar.alpha = 0; 346 | self.toolBar.frame = ({ 347 | CGRect rect = self.toolBar.frame; 348 | rect.origin.x += CGRectGetWidth(rect); 349 | rect; 350 | }); 351 | self.subToolBarLeftView.frame = ({ 352 | CGRect rect = self.subToolBarLeftView.frame; 353 | rect.origin.x = 0; 354 | rect; 355 | }); 356 | foldButton.alpha = 1; 357 | foldButton.frame = CGRectOffset(foldButton.frame, CGRectGetWidth(self.subToolBar.frame), 0); 358 | 359 | } completion:^(BOOL finished) { 360 | 361 | self.toolBar.hidden = YES; 362 | }]; 363 | } 364 | 365 | - (void)hideSubToolBar:(BOOL)animated 366 | { 367 | UIButton *fontItemButton = self.fontButton; 368 | UIButton *leftButton = self.foldButton; 369 | self.toolBar.alpha = 0; 370 | self.toolBar.hidden = NO; 371 | [UIView animateWithDuration: animated ? 0.35 : 0 animations:^{ 372 | 373 | self.subToolBar.alpha = 0; 374 | self.toolBar.alpha = 1.f; 375 | self.toolBar.frame = ({ 376 | CGRect rect = self.toolBar.frame; 377 | rect.origin.x -= CGRectGetWidth(rect); 378 | rect; 379 | }); 380 | self.subToolBarLeftView.frame = [self convertRect:fontItemButton.frame fromView:self.toolBar]; 381 | leftButton.alpha = 0; 382 | leftButton.frame = CGRectOffset(leftButton.frame, -CGRectGetWidth(self.subToolBar.frame), 0); 383 | 384 | } completion:^(BOOL finished) { 385 | 386 | fontItemButton.hidden = NO; 387 | self.subToolBar.hidden = YES; 388 | self.subToolBarLeftView.hidden = YES; 389 | leftButton.frame = CGRectOffset(leftButton.frame, CGRectGetWidth(self.subToolBar.frame), 0); 390 | }]; 391 | } 392 | 393 | - (void)reloadDataWithTypingAttributes:(NSDictionary *)typingAttributes mode:(LMNLineMode)mode isMultiLine:(BOOL)isMultiLine 394 | { 395 | UIFont *font = typingAttributes[NSFontAttributeName]; 396 | BOOL bold = font.bold; 397 | BOOL italic = font.italic; 398 | BOOL underline = (typingAttributes[NSUnderlineStyleAttributeName] != nil); 399 | BOOL strikethrough = (typingAttributes[NSStrikethroughStyleAttributeName] != nil); 400 | 401 | if (isMultiLine || ![@[LMNLineModeTitle, LMNLineModeSubTitle, LMNLineModeContent] containsObject:mode]) { 402 | self.alignment = NSTextAlignmentLeft; 403 | self.alignmentButton.enabled = NO; 404 | } 405 | else { 406 | self.alignmentButton.enabled = YES; 407 | NSParagraphStyle *paragraphStyle = typingAttributes[NSParagraphStyleAttributeName]; 408 | if (paragraphStyle) { 409 | self.alignment = paragraphStyle.alignment; 410 | } 411 | else { 412 | self.alignment = NSTextAlignmentLeft; 413 | } 414 | } 415 | 416 | self.boldButton.selected = bold; 417 | self.italicButton.selected = italic; 418 | self.underlineButton.selected = underline; 419 | self.strikethroughButton.selected = strikethrough; 420 | 421 | [self setMode:mode]; 422 | } 423 | 424 | - (void)setMode:(LMNLineMode)mode 425 | { 426 | if ([_mode isEqualToString:mode]) { 427 | return; 428 | } 429 | _mode = mode; 430 | 431 | UIImage *tNormal = [UIImage imageNamed:@"lmn_tool_t"]; 432 | UIImage *listNormal = [UIImage imageNamed:@"lmn_list"]; 433 | [self.titleButton setImage:tNormal forState:UIControlStateNormal]; 434 | [self.listButton setImage:listNormal forState:UIControlStateNormal]; 435 | 436 | if ([mode isEqualToString:LMNLineModeTitle]) { 437 | UIImage *tTitle = [UIImage imageNamed:@"lmn_tool_title"]; 438 | [self.titleButton setImage:tTitle forState:UIControlStateNormal]; 439 | } 440 | if ([mode isEqualToString:LMNLineModeSubTitle]) { 441 | UIImage *tSubTitle = [UIImage imageNamed:@"lmn_tool_subtitle"]; 442 | [self.titleButton setImage:tSubTitle forState:UIControlStateNormal]; 443 | } 444 | if ([mode isEqualToString:LMNLineModeBullets]) { 445 | UIImage *listDot = [UIImage imageNamed:@"lmn_list_dot"]; 446 | [self.listButton setImage:listDot forState:UIControlStateNormal]; 447 | } 448 | if ([mode isEqualToString:LMNLineModeNumbering]) { 449 | UIImage *listNumber = [UIImage imageNamed:@"lmn_list_number"]; 450 | [self.listButton setImage:listNumber forState:UIControlStateNormal]; 451 | } 452 | } 453 | 454 | - (void)setAlignment:(NSTextAlignment)alignment 455 | { 456 | if (alignment > 2) { 457 | alignment = NSTextAlignmentLeft; 458 | } 459 | if (_alignment == alignment) { 460 | return; 461 | } 462 | _alignment = alignment; 463 | UIImage *image; 464 | switch (alignment) { 465 | case NSTextAlignmentLeft: 466 | image = [UIImage imageNamed:@"lmn_alignment_left"]; 467 | break; 468 | case NSTextAlignmentCenter: 469 | image = [UIImage imageNamed:@"lmn_alignment_center"]; 470 | break; 471 | case NSTextAlignmentRight: 472 | image = [UIImage imageNamed:@"lmn_alignment_right"]; 473 | break; 474 | default: 475 | break; 476 | } 477 | [self.alignmentButton setImage:image forState:UIControlStateNormal]; 478 | } 479 | 480 | @end 481 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNWebViewController.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/12. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LMNWebViewController : UIViewController 12 | 13 | @property (nonatomic, copy) NSString *html; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNote/LMNote/OtherViews/LMNWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNWebViewController.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/12. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNWebViewController.h" 10 | @import WebKit; 11 | 12 | @interface LMNWebViewController () 13 | 14 | @property (nonatomic, strong) UIWebView *webview; 15 | 16 | @end 17 | 18 | @implementation LMNWebViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | } 24 | 25 | - (void)viewDidLayoutSubviews { 26 | [super viewDidLayoutSubviews]; 27 | self.webview.frame = self.view.bounds; 28 | } 29 | 30 | - (UIWebView *)webview 31 | { 32 | if (!_webview) { 33 | _webview = [[UIWebView alloc] init]; 34 | [self.view addSubview:_webview]; 35 | } 36 | return _webview; 37 | } 38 | 39 | - (void)setHtml:(NSString *)html 40 | { 41 | _html = [html copy]; 42 | [self.webview loadHTMLString:html baseURL:nil]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNDraft.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNDraft.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/3. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class LMNTextStorage; 12 | 13 | @interface LMNDraft : LMNItem 14 | 15 | @property (nonatomic, strong) LMNTextStorage *textStorage; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNDraft.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNDraft.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/3. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNDraft.h" 10 | 11 | @implementation LMNDraft 12 | 13 | - (void)encodeWithCoder:(NSCoder *)aCoder 14 | { 15 | [super encodeWithCoder:aCoder]; 16 | [aCoder encodeObject:self.textStorage forKey:@"textStorage"]; 17 | } 18 | 19 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 20 | { 21 | self = [super initWithCoder:aDecoder]; 22 | if (self) { 23 | self.textStorage = [aDecoder decodeObjectForKey:@"textStorage"]; 24 | } 25 | return self; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNFolder.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNFolder.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNItem.h" 10 | 11 | @interface LMNFolder : LMNItem 12 | 13 | @property (nonatomic, strong) NSMutableArray *contents; 14 | - (void)add:(LMNItem *)item; 15 | - (void)remove:(LMNItem *)item; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNFolder.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNFolder.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNFolder.h" 10 | #import "LMNStore.h" 11 | 12 | @implementation LMNFolder 13 | 14 | - (instancetype)initWithUUID:(NSUUID *)uuid name:(NSString *)name date:(NSDate *)date 15 | { 16 | self = [super initWithUUID:uuid name:name date:date]; 17 | if (self) { 18 | self.contents = [NSMutableArray array]; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)setStore:(LMNStore *)store 24 | { 25 | [super setStore:store]; 26 | for (LMNItem *item in self.contents) { 27 | item.store = store; 28 | } 29 | } 30 | 31 | - (void)add:(LMNItem *)item 32 | { 33 | item.parent = self; 34 | if ([self.contents containsObject:item]) { 35 | return; 36 | } 37 | [self.contents addObject:item]; 38 | [self.store save:item userInfo:nil]; 39 | } 40 | 41 | - (void)remove:(LMNItem *)item 42 | { 43 | if (![self.contents containsObject:item]) { 44 | return; 45 | } 46 | item.parent = nil; 47 | [self.contents removeObject:item]; 48 | [self.store save:item userInfo:nil]; 49 | } 50 | 51 | - (void)encodeWithCoder:(NSCoder *)aCoder 52 | { 53 | [super encodeWithCoder:aCoder]; 54 | [aCoder encodeObject:self.contents forKey:@"contents"]; 55 | } 56 | 57 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 58 | { 59 | self = [super initWithCoder:aDecoder]; 60 | if (self) { 61 | self.contents = [aDecoder decodeObjectForKey:@"contents"]; 62 | for (LMNItem *item in self.contents) { 63 | [self add:item]; 64 | } 65 | 66 | if (!self.contents) { 67 | self.contents = [NSMutableArray array]; 68 | } 69 | } 70 | return self; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNItem.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class LMNFolder; 12 | @class LMNStore; 13 | 14 | @interface LMNItem : NSObject 15 | 16 | @property (nonatomic, strong) NSUUID *uuid; 17 | @property (nonatomic, copy) NSString *name; 18 | @property (nonatomic, copy) NSDate *date; 19 | 20 | @property (nonatomic, weak) LMNStore *store; 21 | @property (nonatomic, weak) LMNFolder *parent; 22 | 23 | - (instancetype)initWithUUID:(NSUUID *)uuid name:(NSString *)name date:(NSDate *)date; 24 | 25 | - (void)save; 26 | - (void)delete; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNItem.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNItem.h" 10 | #import "LMNStore.h" 11 | #import "LMNFolder.h" 12 | 13 | @interface LMNItem () 14 | 15 | @end 16 | 17 | @implementation LMNItem 18 | 19 | - (instancetype)initWithUUID:(NSUUID *)uuid name:(NSString *)name date:(NSDate *)date 20 | { 21 | self = [super init]; 22 | if (self) { 23 | self.uuid = uuid; 24 | self.name = name; 25 | self.date = date; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)setParent:(LMNFolder *)parent 31 | { 32 | _parent = parent; 33 | self.store = parent.store; 34 | } 35 | 36 | - (void)save 37 | { 38 | if ([self.parent.contents containsObject:self]) { 39 | [self.store save:self userInfo:nil]; 40 | } 41 | else { 42 | [self.parent add:self]; 43 | } 44 | } 45 | 46 | - (void)delete 47 | { 48 | [self.parent remove:self]; 49 | } 50 | 51 | - (void)encodeWithCoder:(NSCoder *)aCoder 52 | { 53 | [aCoder encodeObject:self.uuid.UUIDString forKey:@"uuid"]; 54 | [aCoder encodeObject:self.name forKey:@"name"]; 55 | [aCoder encodeObject:self.date forKey:@"date"]; 56 | } 57 | 58 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 59 | { 60 | self = [super init]; 61 | if (self) { 62 | self.uuid = [[NSUUID alloc] initWithUUIDString:[aDecoder decodeObjectForKey:@"uuid"]]; 63 | self.name = [aDecoder decodeObjectForKey:@"name"]; 64 | self.date = [aDecoder decodeObjectForKey:@"date"]; 65 | } 66 | return self; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNStore.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LMNFolder.h" 11 | #import "LMNItem.h" 12 | 13 | extern NSString * const LMNStoreVersion; 14 | extern NSString * const LMNStoreVersionArchiveKey; 15 | extern NSString * const LMNStoreDidChangedNotification; 16 | 17 | @interface LMNStore : NSObject 18 | 19 | @property (nonatomic, strong) LMNFolder *rootFolder; 20 | 21 | + (instancetype)shared; 22 | - (instancetype)initWithURL:(NSURL *)url; 23 | 24 | - (NSURL *)imageDirectory; 25 | - (void)save:(LMNItem *)item userInfo:(NSDictionary *)userInfo; 26 | - (void)reload; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/LMNStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNStore.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNStore.h" 10 | 11 | NSString * const LMNStoreVersion = @"1.0"; 12 | NSString * const LMNStoreVersionArchiveKey = @"1.0"; 13 | NSString * const LMNStoreDidChangedNotification = @"LMNStoreDidChanged"; 14 | 15 | @interface LMNStore () 16 | 17 | @property (nonatomic, strong) NSURL *baseURL; 18 | 19 | @end 20 | 21 | @implementation LMNStore 22 | 23 | static NSString * const kStoreLocation = @"archives.dat"; 24 | static NSString * const kStoreImageDirectoryName = @"archives_images"; 25 | 26 | + (instancetype)shared 27 | { 28 | static dispatch_once_t once; 29 | static id instance; 30 | dispatch_once(&once, ^{ 31 | NSURL *documentDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:NULL]; 32 | instance = [[self alloc] initWithURL:documentDirectory]; 33 | }); 34 | return instance; 35 | } 36 | 37 | - (instancetype)initWithURL:(NSURL *)url 38 | { 39 | self = [super init]; 40 | if (self) { 41 | self.baseURL = url; 42 | [self loadData]; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)loadData 48 | { 49 | NSURL *fileURL = [self.baseURL URLByAppendingPathComponent:kStoreLocation]; 50 | NSData *data = [NSData dataWithContentsOfURL:fileURL]; 51 | self.rootFolder = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 52 | if (!self.rootFolder) { 53 | self.rootFolder = [[LMNFolder alloc] initWithUUID:[NSUUID UUID] name:@"" date:[NSDate date]]; 54 | } 55 | self.rootFolder.store = self; 56 | } 57 | 58 | - (void)save:(LMNItem *)item userInfo:(NSDictionary *)userInfo 59 | { 60 | NSURL *file = [self.baseURL URLByAppendingPathComponent:kStoreLocation]; 61 | if (![NSKeyedArchiver archiveRootObject:self.rootFolder toFile:file.path]) { 62 | // 保存失败 63 | return; 64 | } 65 | [[NSNotificationCenter defaultCenter] postNotificationName:LMNStoreDidChangedNotification 66 | object:self 67 | userInfo:userInfo]; 68 | } 69 | 70 | - (void)reload 71 | { 72 | [self loadData]; 73 | } 74 | 75 | - (NSURL *)imageDirectory 76 | { 77 | NSURL *url = [self.baseURL URLByAppendingPathComponent:kStoreImageDirectoryName]; 78 | NSError *error; 79 | [[NSFileManager defaultManager] createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:&error]; 80 | NSAssert(!error, @"%@", error); 81 | return url; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/NSTextAttachment+LMNStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextAttachment+LMNStore.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/6. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSTextAttachment (LMNStore) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/NSTextAttachment+LMNStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextAttachment+LMNStore.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/6. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "NSTextAttachment+LMNStore.h" 10 | #import 11 | 12 | @implementation NSTextAttachment (LMNStore) 13 | 14 | + (void)load 15 | { 16 | Method oldMethod = class_getInstanceMethod(self, @selector(initWithCoder:)); 17 | Method newMethod = class_getInstanceMethod(self, @selector(lmn_initWithCoder:)); 18 | method_exchangeImplementations(oldMethod, newMethod); 19 | 20 | oldMethod = class_getInstanceMethod(self, @selector(encodeWithCoder:)); 21 | newMethod = class_getInstanceMethod(self, @selector(lmn_encodeWithCoder:)); 22 | method_exchangeImplementations(oldMethod, newMethod); 23 | } 24 | 25 | - (instancetype)lmn_initWithCoder:(NSCoder *)aDecoder 26 | { 27 | NSTextAttachment *instance = [self lmn_initWithCoder:aDecoder]; 28 | if (instance) { 29 | instance.bounds = [aDecoder decodeCGRectForKey:@"bounds"]; 30 | } 31 | return instance; 32 | } 33 | 34 | - (void)lmn_encodeWithCoder:(NSCoder *)aCoder 35 | { 36 | [self lmn_encodeWithCoder:aCoder]; 37 | [aCoder encodeCGRect:self.bounds forKey:@"bounds"]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/UIImage+LMNStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+LMNStore.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/6. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (LMNStore) 12 | 13 | @property (nonatomic, strong) NSString *lmn_fileName; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNote/LMNote/Store/UIImage+LMNStore.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+LMNStore.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/6. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "UIImage+LMNStore.h" 10 | #import 11 | 12 | @implementation UIImage (LMNStore) 13 | 14 | @dynamic lmn_fileName; 15 | 16 | - (NSString *)lmn_fileName 17 | { 18 | return objc_getAssociatedObject(self, @selector(lmn_fileName)); 19 | } 20 | 21 | - (void)setLmn_fileName:(NSString *)lmn_fileName 22 | { 23 | objc_setAssociatedObject(self, @selector(lmn_fileName), lmn_fileName, OBJC_ASSOCIATION_COPY_NONATOMIC); 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/LMNTextStorage+Export.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextStorage+Export.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/12. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNTextStorage.h" 10 | 11 | @interface LMNTextStorage (Export) 12 | 13 | - (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/LMNTextStorage+Export.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextStorage+Export.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/7/12. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNTextStorage+Export.h" 10 | #import "UIFont+LMNote.h" 11 | #import "LMNLineChain.h" 12 | #import "LMNCheckboxLine.h" 13 | 14 | @implementation LMNTextStorage (Export) 15 | 16 | // 导出成 HTML 字符串,可以根据具体情况自由添加 css。 17 | - (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion 18 | { 19 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{ 20 | NSMutableString *html = [NSMutableString string]; 21 | LMNLine *line = self.chain.rootLine; 22 | do { 23 | NSRange range = line.range; 24 | if (line.next) { 25 | range.length -= 1; 26 | } 27 | NSAttributedString *attributedText = [self attributedSubstringFromRange:range]; 28 | if ([line isKindOfMode:LMNLineModeModeImage]) { 29 | // TODO: 图片部分逻辑未处理。 30 | 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"; 31 | [html appendFormat:@"", imgSrc]; 32 | } 33 | else { 34 | NSMutableString *lineHTML = [NSMutableString string]; 35 | if (attributedText.length > 0) { 36 | NSRange effectiveRange = NSMakeRange(0, 0); 37 | do { 38 | NSDictionary *attributes = [attributedText attributesAtIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange]; 39 | NSString *text = [[attributedText string] substringWithRange:effectiveRange]; 40 | UIFont *font = attributes[NSFontAttributeName]; 41 | BOOL underline = [attributes[NSUnderlineStyleAttributeName] boolValue]; 42 | BOOL strikethrough = [attributes[NSStrikethroughStyleAttributeName] integerValue] == 1; 43 | NSString *fragment = [self htmlWithContent:text font:font underline:underline strikethrough:strikethrough]; 44 | [lineHTML appendString:fragment]; 45 | } while (NSMaxRange(effectiveRange) < attributedText.length); 46 | } 47 | NSString *styleClass = @"content"; 48 | if ([line isKindOfMode:LMNLineModeTitle]) { 49 | styleClass = @"title"; 50 | } 51 | else if ([line isKindOfMode:LMNLineModeSubTitle]) { 52 | styleClass = @"subtitle"; 53 | } 54 | 55 | if ([line isKindOfMode:LMNLineModeNumbering]) { 56 | if (![line.prev isKindOfMode:LMNLineModeNumbering]) { 57 | [html appendString:@"
    "]; 58 | } 59 | [html appendFormat:@"
  1. %@

    ", styleClass, lineHTML]; 60 | if (![line.next isKindOfMode:LMNLineModeNumbering]) { 61 | [html appendString:@"
"]; 62 | } 63 | } 64 | else if ([line isKindOfMode:LMNLineModeBullets]) { 65 | if (![line.prev isKindOfMode:LMNLineModeBullets]) { 66 | [html appendString:@"
    "]; 67 | } 68 | [html appendFormat:@"
  • %@

    ", styleClass, lineHTML]; 69 | if (![line.next isKindOfMode:LMNLineModeBullets]) { 70 | [html appendString:@"
"]; 71 | } 72 | } 73 | else if ([line isKindOfMode:LMNLineModeCheckbox]) { 74 | NSString *checked = [(LMNCheckboxLine *)line checkboxSelected] ? @"checked " : @""; 75 | [html appendFormat:@"%@

", styleClass, checked, lineHTML]; 76 | } 77 | else { 78 | [html appendFormat:@"

%@

", styleClass, lineHTML]; 79 | } 80 | } 81 | line = line.next; 82 | } while (line); 83 | 84 | dispatch_async(dispatch_get_main_queue(), ^{ 85 | [html insertString:@"" atIndex:0]; 86 | [html appendString:@""]; 87 | completion(YES, [html copy]); 88 | }); 89 | }); 90 | } 91 | 92 | - (NSString *)htmlWithContent:(NSString *)content 93 | font:(UIFont *)font 94 | underline:(BOOL)underline 95 | strikethrough:(BOOL)strikethrough 96 | { 97 | if (content.length == 0 || font == nil) { 98 | return @""; 99 | } 100 | NSString *html = content; 101 | if (font.bold) { 102 | html = [NSString stringWithFormat:@"%@", html]; 103 | } 104 | if (font.italic) { 105 | html = [NSString stringWithFormat:@"%@", html]; 106 | } 107 | if (underline) { 108 | html = [NSString stringWithFormat:@"%@", html]; 109 | } 110 | if (strikethrough) { 111 | html = [NSString stringWithFormat:@"%@", html]; 112 | } 113 | return html; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/LMNTextStorage.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextStorage.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/3/12. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LMNLineModes.h" 11 | 12 | @class LMNLine; 13 | @class LMNLineChain; 14 | 15 | @interface LMNTextStorage : NSTextStorage 16 | 17 | @property (nonatomic, strong, readonly) LMNLineChain *chain; 18 | @property (nonatomic, assign, readonly) BOOL inProcessEditing; 19 | 20 | - (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range; 21 | - (void)setTextAlignment:(NSTextAlignment)alignment forRange:(NSRange)range; 22 | 23 | - (LMNLine *)lineAtLocation:(NSUInteger)location; 24 | - (void)updateNumberingStartWithLine:(LMNLine *)line; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/LMNTextStorage.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextStorage.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/3/12. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNTextStorage.h" 10 | #import "LMNLineChain+Numbering.h" 11 | #import "LMNStore.h" 12 | 13 | #import "LMNTextLine.h" 14 | #import "LMNNumberingLine.h" 15 | 16 | @interface LMNTextStorage () 17 | 18 | @property (nonatomic, strong) LMNLineChain *chain; 19 | @property (nonatomic, assign, readwrite) BOOL inProcessEditing; 20 | 21 | @end 22 | 23 | @implementation LMNTextStorage 24 | { 25 | NSTextStorage *_imp; 26 | } 27 | 28 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 29 | { 30 | self = [super init]; 31 | if (self) { 32 | _imp = [aDecoder decodeObjectForKey:@"imp"]; 33 | _chain = [aDecoder decodeObjectForKey:@"chain"]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)encodeWithCoder:(NSCoder *)aCoder 39 | { 40 | [aCoder encodeObject:_imp forKey:@"imp"]; 41 | [aCoder encodeObject:_chain forKey:@"chain"]; 42 | } 43 | 44 | - (instancetype)init 45 | { 46 | self = [super init]; 47 | if (self) { 48 | _imp = [[NSTextStorage alloc] init]; 49 | _chain = [[LMNLineChain alloc] init]; 50 | } 51 | return self; 52 | } 53 | 54 | - (NSString *)string 55 | { 56 | return _imp.string; 57 | } 58 | 59 | - (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range 60 | { 61 | return [_imp attributesAtIndex:location effectiveRange:range]; 62 | } 63 | 64 | - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str 65 | { 66 | [_imp replaceCharactersInRange:range withString:str]; 67 | [self edited:NSTextStorageEditedCharacters range:range changeInLength:(NSInteger)str.length - (NSInteger)range.length]; 68 | } 69 | 70 | - (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range 71 | { 72 | [_imp setAttributes:attrs range:range]; 73 | [self edited:NSTextStorageEditedAttributes range:range changeInLength:0]; 74 | } 75 | 76 | - (void)processEditing 77 | { 78 | self.inProcessEditing = YES; 79 | 80 | if (self.editedMask & NSTextStorageEditedCharacters && 81 | !(self.editedRange.length == 0 && self.changeInLength == 0)) { 82 | 83 | NSRange editedRange = self.editedRange; 84 | NSRange replacedRange = editedRange; 85 | replacedRange.length -= self.changeInLength; 86 | 87 | NSString *originText = self.chain.text; 88 | NSString *replacementText = [self.string substringWithRange:editedRange]; 89 | NSString *replacedText = [originText substringWithRange:replacedRange]; 90 | 91 | LMNLine *forePart = [self.chain lineAtLocation:replacedRange.location]; // 前部分 92 | LMNLine *backPart = [self.chain lineAtLocation:NSMaxRange(replacedRange)]; // 后部分 93 | 94 | BOOL shouldUpdateNumbering = NO; 95 | if ([forePart isKindOfClass:[LMNNumberingLine class]] || 96 | [backPart isKindOfClass:[LMNNumberingLine class]]) { 97 | shouldUpdateNumbering = YES; 98 | } 99 | // 重新整理段落个数 100 | NSInteger replacedCount = [replacedText componentsSeparatedByString:@"\n"].count; 101 | NSInteger replacementCount = [replacementText componentsSeparatedByString:@"\n"].count; 102 | NSInteger changeInCount = MAX(replacementCount, 1) - MAX(replacedCount, 1); 103 | LMNLine *line = forePart; 104 | LMNLine *next = backPart ? backPart.next : forePart.next; 105 | for (NSInteger i = 0; i < changeInCount; i ++) { 106 | 107 | LMNLine *newline = nil; 108 | if (i == 0 && [replacementText hasPrefix:@"\n"]) { 109 | newline = [LMNLine lineWithMode:forePart.mode]; 110 | [newline inheritFromLine:line]; // 继承上一行的部分属性 111 | } 112 | else { 113 | newline = [LMNLine line]; 114 | } 115 | line.next = newline; 116 | line = newline; 117 | } 118 | line.next = next; 119 | [self.chain updateWithText:self.string]; 120 | if (shouldUpdateNumbering && changeInCount != 0) { 121 | [self.chain updateNumberings]; 122 | } 123 | if (changeInCount < 0 && forePart.attributes) { 124 | NSRange range = self.editedRange; 125 | range.length = NSMaxRange(forePart.range) - range.location; 126 | [self addAttributes:forePart.attributes range:range]; 127 | } 128 | } 129 | [super processEditing]; 130 | self.inProcessEditing = NO; 131 | } 132 | 133 | - (void)fixAttributesInRange:(NSRange)range 134 | { 135 | [super fixAttributesInRange:range]; 136 | 137 | NSUInteger index = range.location; 138 | while (NSLocationInRange(index, range)) { 139 | LMNLine *line = [self lineAtLocation:index]; 140 | if ([self attribute:NSAttachmentAttributeName atIndex:index effectiveRange:NULL]) { 141 | [self removeAttribute:NSParagraphStyleAttributeName range:line.range]; 142 | } 143 | else { 144 | NSParagraphStyle *paragraphStyle = line.attributes[NSParagraphStyleAttributeName]; 145 | if (paragraphStyle) { 146 | [self addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:line.range]; 147 | } 148 | } 149 | index = NSMaxRange(line.range); 150 | }; 151 | } 152 | 153 | #pragma mark - private 154 | 155 | - (void)enumerateParagraphsInRange:(NSRange)range usingBlock:(void (^)(NSRange paragraphRange))block 156 | { 157 | if (!block) { 158 | return; 159 | } 160 | NSRange paragraphRange = NSMakeRange(range.location, 0); 161 | do { 162 | paragraphRange = [self.string paragraphRangeForRange:paragraphRange]; 163 | // 执行 block 164 | block(paragraphRange); 165 | 166 | paragraphRange.location = NSMaxRange(paragraphRange); 167 | paragraphRange.length = 0; 168 | } while (NSLocationInRange(paragraphRange.location, range)); 169 | 170 | if ([[self.string substringWithRange:range] isEqualToString:@"\n"]) { 171 | paragraphRange = [self.string paragraphRangeForRange:paragraphRange]; 172 | block(paragraphRange); 173 | } 174 | } 175 | 176 | - (void)updateLineDisplay:(LMNLine *)line 177 | { 178 | [self setAttributes:line.attributes range:line.range]; 179 | NSRange replaceRange = NSMakeRange(line.range.location, 0); 180 | [self replaceCharactersInRange:replaceRange withString:@""]; // 强制刷新行 181 | } 182 | 183 | #pragma mark - public 184 | 185 | - (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range 186 | { 187 | __block BOOL shouldUpdateNumbering = NO; 188 | [self enumerateParagraphsInRange:range usingBlock:^(NSRange paragraphRange) { 189 | 190 | LMNLine *line = [self.chain lineAtLocation:paragraphRange.location]; 191 | if ([line isKindOfMode:mode]) { 192 | return; 193 | } 194 | LMNLine *newline = [LMNLine lineWithMode:mode]; 195 | [newline insteadOfLine:line]; 196 | 197 | if ([line isKindOfMode:LMNLineModeNumbering] || [newline isKindOfMode:LMNLineModeNumbering]) { 198 | shouldUpdateNumbering = YES; 199 | } 200 | [self updateLineDisplay:newline]; 201 | }]; 202 | [self.chain updateWithText:self.string]; 203 | if (shouldUpdateNumbering) { 204 | [self.chain updateNumberings]; 205 | } 206 | } 207 | 208 | - (void)setTextAlignment:(NSTextAlignment)alignment forRange:(NSRange)range 209 | { 210 | LMNLine *line = [self lineAtLocation:range.location]; 211 | if ([line isKindOfClass:[LMNTextLine class]]) { 212 | [(LMNTextLine *)line setTextAlignment:alignment]; 213 | } 214 | [self updateLineDisplay:line]; 215 | } 216 | 217 | - (LMNLine *)lineAtLocation:(NSUInteger)location 218 | { 219 | return [self.chain lineAtLocation:location]; 220 | } 221 | 222 | - (void)updateNumberingStartWithLine:(LMNLine *)line 223 | { 224 | [self.chain updateNumberingStartWithLine:line]; 225 | } 226 | 227 | @end 228 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/LMNTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextView.h 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LMNLineModes.h" 11 | 12 | @class LMNTextStorage; 13 | @class LMNImageView; 14 | 15 | @interface LMNTextView : UITextView 16 | 17 | - (instancetype)initWithTextStorage:(LMNTextStorage *)textStorage; 18 | 19 | - (LMNLineMode)lineModeForRange:(NSRange)range; 20 | - (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range; 21 | - (void)setAttributesForSelection:(NSDictionary *)attributes; 22 | - (void)setTextAlignmentForSelection:(NSTextAlignment)alignment; 23 | - (LMNImageView *)insertImage:(UIImage *)image atIndex:(NSInteger)index; 24 | - (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/LMNTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMNTextView.m 3 | // LMNote 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "LMNTextView.h" 10 | #import "LMNTextStorage.h" 11 | #import "LMNLine.h" 12 | #import "LMNImageView.h" 13 | #import "LMNTextStorage+Export.h" 14 | #import 15 | 16 | #import "LMNSpecialLine.h" 17 | #import "LMNImageLine.h" 18 | #import "LMNTextLine.h" 19 | 20 | #define macro_commitUpdating(code); \ 21 | BOOL ignore = self.ignoreUpdatingExclusionPaths; \ 22 | self.ignoreUpdatingExclusionPaths = YES; \ 23 | code \ 24 | self.ignoreUpdatingExclusionPaths = ignore; \ 25 | if (!ignore) { \ 26 | [self updateExclusionPathsIfNeed]; \ 27 | } \ 28 | 29 | @interface LMNTextView () 30 | 31 | @property (nonatomic, weak) LMNImageView *editingImageView; 32 | 33 | @property (nonatomic, assign) BOOL needUpdateExclusionPaths; 34 | @property (nonatomic, assign) BOOL ignoreUpdatingExclusionPaths; 35 | 36 | @end 37 | 38 | @implementation LMNTextView 39 | { 40 | LMNTextStorage *_textStorage; 41 | } 42 | 43 | - (instancetype)init 44 | { 45 | return [self initWithTextStorage:nil]; 46 | } 47 | 48 | - (instancetype)initWithTextStorage:(LMNTextStorage *)textStorage 49 | { 50 | if (!textStorage) { 51 | textStorage = [[LMNTextStorage alloc] init]; 52 | } 53 | NSTextContainer *textContainer = [[NSTextContainer alloc] init]; 54 | NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; 55 | [textStorage addLayoutManager:layoutManager]; 56 | [layoutManager addTextContainer:textContainer]; 57 | self = [super initWithFrame:CGRectZero textContainer:textContainer]; 58 | if (self) { 59 | _textStorage = textStorage; 60 | self.textContainerInset = UIEdgeInsetsMake(10.f, 15.f, 10.f, 15.f); 61 | self.typingAttributes = [self typingAttributesAtLocation:0]; 62 | [self addObservers]; 63 | self.needUpdateExclusionPaths = YES; 64 | } 65 | return self; 66 | } 67 | 68 | - (void)layoutSubviews 69 | { 70 | [super layoutSubviews]; 71 | [self updateExclusionPathsIfNeed]; 72 | } 73 | 74 | - (void)dealloc 75 | { 76 | [self removeObservers]; 77 | } 78 | 79 | #pragma mark - observer 80 | 81 | - (void)addObservers 82 | { 83 | [[NSNotificationCenter defaultCenter] addObserver:self 84 | selector:@selector(textStorageDidProcessEditing:) 85 | name:NSTextStorageDidProcessEditingNotification 86 | object:nil]; 87 | [[NSNotificationCenter defaultCenter] addObserver:self 88 | selector:@selector(keyboardWillShow:) 89 | name:UIKeyboardWillShowNotification 90 | object:nil]; 91 | [_textStorage addObserver:self 92 | forKeyPath:@"inProcessEditing" 93 | options:NSKeyValueObservingOptionNew 94 | context:NULL]; 95 | } 96 | 97 | - (void)removeObservers 98 | { 99 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 100 | [_textStorage removeObserver:self forKeyPath:@"inProcessEditing"]; 101 | } 102 | 103 | #pragma mark - NSTextStorageDidProcessEditingNotification 104 | 105 | - (void)textStorageDidProcessEditing:(NSNotification *)notification 106 | { 107 | if (notification.object == _textStorage) { 108 | 109 | if (!_textStorage.inProcessEditing) { 110 | [self updateExclusionPathsIfNeed]; 111 | } 112 | else { 113 | self.needUpdateExclusionPaths = YES; 114 | } 115 | } 116 | } 117 | 118 | - (void)keyboardWillShow:(NSNotification *)notification 119 | { 120 | [self.editingImageView endEditing]; 121 | self.editingImageView = nil; 122 | } 123 | 124 | #pragma mark - KVC 125 | 126 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 127 | { 128 | if (object == _textStorage && [keyPath isEqualToString:@"inProcessEditing"]) { 129 | BOOL inProcessEditing = [change[NSKeyValueChangeNewKey] boolValue]; 130 | if (!inProcessEditing) { 131 | [self updateExclusionPathsIfNeed]; 132 | } 133 | } 134 | } 135 | 136 | #pragma mark - update exclusionPaths 137 | 138 | static CGFloat const kDefaultTextInset = 5.f; // 默认文字会有5.f的缩进 139 | 140 | - (void)updateExclusionPaths 141 | { 142 | NSString *text = self.text; 143 | UIEdgeInsets textContainerInset = self.textContainerInset; 144 | NSTextContainer *textContainer = self.textContainer; 145 | LMNTextStorage *textStorage = _textStorage; 146 | 147 | __block CGFloat yOffset = 0; 148 | __block NSRange range = NSMakeRange(0, 0); 149 | CGFloat limitWidth = textContainer.size.width - kDefaultTextInset * 2; 150 | 151 | NSMutableArray *exclusionPaths = [NSMutableArray array]; 152 | [text enumerateLinesUsingBlock:^(NSString *textLine, BOOL *stop) { 153 | range.length = textLine.length; 154 | LMNLine *line = [textStorage lineAtLocation:range.location]; 155 | NSAttributedString *attributedText = [textStorage attributedSubstringFromRange:range]; 156 | CGFloat lineHeight = 0; 157 | UIFont *font = line.attributes[NSFontAttributeName]; 158 | NSParagraphStyle *paragraphStyle = line.attributes[NSParagraphStyleAttributeName]; 159 | if (range.length == 0) { 160 | lineHeight = font.lineHeight; 161 | } 162 | else { 163 | CGSize limitSize = CGSizeZero; 164 | if ([line isKindOfClass:[LMNSpecialLine class]]) { 165 | limitSize.width = limitWidth - ((LMNSpecialLine *)line).intrinsicLeftSize.width; 166 | } 167 | else { 168 | limitSize.width = limitWidth; 169 | } 170 | lineHeight = [attributedText boundingRectWithSize:limitSize 171 | options:NSStringDrawingUsesLineFragmentOrigin 172 | context:NULL].size.height; 173 | if (font) { 174 | // 存在粗体、下划线时,考虑由于 lineSpacing 对计算结果的影响 175 | id tmp = [attributedText mutableCopy]; 176 | [tmp addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, attributedText.length)]; 177 | [tmp removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, attributedText.length)]; 178 | [tmp removeAttribute:NSStrikethroughStyleAttributeName range:NSMakeRange(0, attributedText.length)]; 179 | 180 | attributedText = tmp; 181 | CGFloat estimatedLineHeight = [tmp boundingRectWithSize:limitSize options:NSStringDrawingUsesLineFragmentOrigin context:NULL].size.height; 182 | CGFloat spacing = paragraphStyle.lineSpacing + paragraphStyle.paragraphSpacing; 183 | if (lineHeight - estimatedLineHeight <= spacing) { 184 | lineHeight = estimatedLineHeight; 185 | } 186 | } 187 | } 188 | if (paragraphStyle && ![line isKindOfClass:[LMNImageLine class]]) { 189 | lineHeight += paragraphStyle.lineSpacing; 190 | lineHeight += paragraphStyle.paragraphSpacing; 191 | lineHeight += paragraphStyle.paragraphSpacingBefore; 192 | if (range.location == 0) { 193 | // 首行没有段前间距 194 | yOffset -= paragraphStyle.paragraphSpacingBefore; 195 | } 196 | } 197 | 198 | if ([line isKindOfClass:[LMNTextLine class]]) { 199 | yOffset += lineHeight; 200 | range.location = NSMaxRange(range) + 1; 201 | return; 202 | } 203 | 204 | // 给行样式添加 exclusionPath 205 | CGRect rect = CGRectZero; 206 | rect.origin.y = ceilf(yOffset); 207 | rect.size.height = floorf(yOffset + lineHeight) - rect.origin.y; 208 | if ([line isKindOfClass:[LMNSpecialLine class]]) { 209 | LMNSpecialLine *specialLine = (LMNSpecialLine *)line; 210 | rect.size.width = [specialLine intrinsicLeftSize].width; 211 | // 1.f 是为小数精度做微调。 212 | UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectInset(rect, 0, 1.f)]; 213 | [exclusionPaths addObject:path]; 214 | 215 | if (!specialLine.leftView) { 216 | [specialLine loadLeftView]; 217 | } 218 | if (!specialLine.leftView.superview) { 219 | [self addSubview:specialLine.leftView]; 220 | } 221 | rect.origin.x = textContainerInset.left; 222 | rect.origin.y += textContainerInset.top; 223 | rect.size = [specialLine intrinsicLeftSize]; 224 | if (rect.size.height == 0) { 225 | rect.size.height = lineHeight; 226 | } 227 | specialLine.leftView.frame = rect; 228 | } 229 | else if ([line isKindOfClass:[LMNImageLine class]]) { 230 | // 图片 231 | LMNImageLine *imageLine = (LMNImageLine *)line; 232 | LMNImageView *imageView = imageLine.bindingImageView; 233 | CGFloat width = CGRectGetWidth(self.frame); 234 | UIEdgeInsets textContainerInset = self.textContainerInset; 235 | width -= (textContainerInset.left + textContainerInset.right + kDefaultTextInset * 2); 236 | rect.size = [LMNImageView sizeThatFit:imageLine.image limitWidth:width]; 237 | if (imageView == nil && imageLine.image != nil) { 238 | imageView = [[LMNImageView alloc] initWithImage:imageLine.image]; 239 | imageView.delegate = self; 240 | [self addSubview:imageView]; 241 | [imageLine bindImageView:imageView]; 242 | } 243 | if (imageView) { 244 | rect.origin.x = kDefaultTextInset + textContainerInset.left; 245 | rect.origin.y += textContainerInset.top; 246 | imageView.frame = rect; 247 | } 248 | } 249 | yOffset += lineHeight; 250 | range.location = NSMaxRange(range) + 1; 251 | }]; 252 | 253 | // 派发到下次任务中,不然会 Crash 254 | dispatch_async(dispatch_get_main_queue(), ^{ 255 | self.scrollEnabled = NO; 256 | self.textContainer.exclusionPaths = exclusionPaths; 257 | self.scrollEnabled = YES; 258 | }); 259 | } 260 | 261 | - (void)updateExclusionPathsIfNeed 262 | { 263 | if (CGRectGetWidth(self.bounds) == 0) { 264 | return; 265 | } 266 | if (self.needUpdateExclusionPaths && !self.ignoreUpdatingExclusionPaths) { 267 | [self updateExclusionPaths]; 268 | self.needUpdateExclusionPaths = NO; 269 | } 270 | } 271 | 272 | #pragma mark - extraLine 273 | 274 | - (BOOL)isSelectedExtraLine 275 | { 276 | return ([self.text hasSuffix:@"\n"] && NSMaxRange(self.selectedRange) == self.text.length); 277 | } 278 | 279 | // 通过 [NSLayoutManager -drawBackgroundForGlyphRange:atPoint:] 来给行绘制项目符号,但对于没有内容最后一行(extraLine)则不会触发绘制方法,这里添加特殊处理,给 extraLine 添加一个 "\n" 以触发绘制方法。 280 | - (void)appendingLineBreakForExtraLine 281 | { 282 | NSRange paragraphRange = [self.text paragraphRangeForRange:self.selectedRange]; 283 | NSString *endingText = [self.text substringWithRange:paragraphRange]; 284 | if (![endingText containsString:@"\n"]) { 285 | // 选中行为最后一行 286 | NSRange range = NSMakeRange(self.text.length, 0); 287 | LMNLine *lastLine = [_textStorage lineAtLocation:range.location]; 288 | if ([lastLine isKindOfClass:[LMNSpecialLine class]]) { 289 | [_textStorage replaceCharactersInRange:range withAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:lastLine.attributes]]; 290 | [[LMNLine line] insteadOfLine:lastLine.next]; 291 | } 292 | } 293 | } 294 | 295 | #pragma mark - override 296 | 297 | - (BOOL)shouldDeleteBackward 298 | { 299 | NSRange selectedRange = self.selectedRange; 300 | if (selectedRange.length == 0 && selectedRange.location > 0) { 301 | LMNLine *line = [_textStorage lineAtLocation:selectedRange.location - 1]; 302 | if (NSMaxRange(line.range) == selectedRange.location && 303 | [line isKindOfClass:[LMNImageLine class]]) { 304 | [((LMNImageLine *)line).bindingImageView beginEditing]; 305 | return NO; 306 | } 307 | } 308 | return YES; 309 | } 310 | 311 | - (void)deleteBackward 312 | { 313 | if (![self shouldDeleteBackward]) { 314 | return; 315 | } 316 | if (NSEqualRanges(self.selectedRange, NSMakeRange(0, 0))) { 317 | LMNLine *line = [_textStorage lineAtLocation:0]; 318 | if ([line isKindOfClass:[LMNSpecialLine class]]) { 319 | [_textStorage setLineMode:LMNLineModeContent forRange:NSMakeRange(0, 0)]; 320 | return; 321 | } 322 | } 323 | macro_commitUpdating({ 324 | [super deleteBackward]; 325 | [self appendingLineBreakForExtraLine]; 326 | }); 327 | } 328 | 329 | - (void)insertText:(NSString *)text 330 | { 331 | #pragma warning - 粘贴不走该逻辑 332 | if ([text isEqualToString:@"\n"] && self.selectedRange.length == 0) { 333 | 334 | LMNLine *line = [_textStorage lineAtLocation:self.selectedRange.location]; 335 | if ([line isKindOfClass:[LMNSpecialLine class]]) { 336 | NSString *prevStr = self.selectedRange.location == 0 ? @"\n" : [self.text substringWithRange:NSMakeRange(self.selectedRange.location - 1, 1)]; 337 | NSString *nextStr = [self.text substringWithRange:NSMakeRange(self.selectedRange.location, 1)]; 338 | if ([prevStr isEqualToString:@"\n"] && [nextStr isEqualToString:@"\n"]) { 339 | [self setLineMode:LMNLineModeContent forRange:self.selectedRange]; 340 | return; 341 | } 342 | } 343 | } 344 | macro_commitUpdating({ 345 | [super insertText:text]; 346 | [self appendingLineBreakForExtraLine]; 347 | }); 348 | } 349 | 350 | #pragma mark - private 351 | 352 | - (NSDictionary *)typingAttributesAtLocation:(NSUInteger)location 353 | { 354 | return [_textStorage lineAtLocation:location].attributes; 355 | } 356 | 357 | #pragma mark - public method 358 | 359 | - (LMNLineMode)lineModeForRange:(NSRange)range 360 | { 361 | LMNLine *line = [_textStorage lineAtLocation:range.location]; 362 | return line.mode; 363 | } 364 | 365 | - (void)setLineMode:(LMNLineMode)mode forRange:(NSRange)range 366 | { 367 | macro_commitUpdating({ 368 | [_textStorage setLineMode:mode forRange:range]; 369 | [self appendingLineBreakForExtraLine]; 370 | }); 371 | self.typingAttributes = [self typingAttributesAtLocation:range.location]; 372 | } 373 | 374 | - (void)setAttributesForSelection:(NSDictionary *)attributes 375 | { 376 | [_textStorage setAttributes:attributes range:self.selectedRange]; 377 | } 378 | 379 | - (void)setTextAlignmentForSelection:(NSTextAlignment)alignment 380 | { 381 | LMNTextStorage *textStorage = (LMNTextStorage *)_textStorage; 382 | [textStorage setTextAlignment:alignment forRange:self.selectedRange]; 383 | } 384 | 385 | - (void)exportHTML:(void (^)(BOOL succeed, NSString *html))completion 386 | { 387 | [_textStorage exportHTML:completion]; 388 | } 389 | 390 | #pragma mark - image 391 | 392 | - (LMNImageView *)insertImage:(UIImage *)image atIndex:(NSInteger)index 393 | { 394 | if (index >= self.text.length) { 395 | index = self.text.length; 396 | } 397 | 398 | CGFloat width = CGRectGetWidth(self.frame); 399 | UIEdgeInsets textContainerInset = self.textContainerInset; 400 | width -= (textContainerInset.left + textContainerInset.right + kDefaultTextInset * 2); 401 | CGSize size = [LMNImageView sizeThatFit:image limitWidth:width]; 402 | 403 | LMNImageView *imageView = [[LMNImageView alloc] initWithImage:image]; 404 | imageView.delegate = self; 405 | [self addSubview:imageView]; 406 | 407 | NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; 408 | CGRect bounds = CGRectZero; 409 | bounds.size = size; 410 | attachment.image = [UIImage new]; 411 | attachment.bounds = bounds; 412 | NSAttributedString *imgStr = [NSAttributedString attributedStringWithAttachment:attachment]; 413 | 414 | LMNLine *lineAtIndex = [_textStorage lineAtLocation:index]; 415 | NSMutableAttributedString *replacementString = [[NSMutableAttributedString alloc] init]; 416 | [replacementString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:lineAtIndex.attributes]]; 417 | BOOL inCurrentLine = NO; 418 | NSUInteger insertLocation = NSMaxRange(lineAtIndex.range) - 1; 419 | if (lineAtIndex.range.location == index) { 420 | // 如果光标位置是行首,则图片在该行之前 421 | inCurrentLine = YES; 422 | insertLocation = lineAtIndex.range.location; 423 | } 424 | else if (lineAtIndex.next == nil) { 425 | // 最后一行,则在图片之后新增一行。 426 | [replacementString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:[LMNLine line].attributes]]; 427 | insertLocation ++; 428 | } 429 | [replacementString insertAttributedString:imgStr atIndex:inCurrentLine ? 0 : 1]; 430 | 431 | macro_commitUpdating({ 432 | [_textStorage replaceCharactersInRange:NSMakeRange(insertLocation, 0) withAttributedString:replacementString]; 433 | 434 | LMNLine *line = [_textStorage lineAtLocation:index]; 435 | if (!inCurrentLine) { 436 | line = line.next; 437 | } 438 | if (line.next.range.length == 0) { 439 | LMNLine *newline = [LMNLine lineWithMode:LMNLineModeContent]; 440 | [newline insteadOfLine:line.next]; 441 | } 442 | LMNLine *newline = [LMNLine lineWithMode:LMNLineModeModeImage]; 443 | [newline insteadOfLine:line]; 444 | [(LMNImageLine *)newline bindImageView:imageView]; 445 | [_textStorage updateNumberingStartWithLine:newline.next]; 446 | }); 447 | 448 | return imageView; 449 | } 450 | 451 | #pragma mark 452 | 453 | - (void)lmn_imageViewBeginEditing:(LMNImageView *)imageView 454 | { 455 | [self.editingImageView endEditing]; 456 | self.editingImageView = imageView; 457 | [self resignFirstResponder]; 458 | } 459 | 460 | - (void)lmn_imageViewEndEditing:(LMNImageView *)imageView 461 | { 462 | self.editingImageView = nil; 463 | } 464 | 465 | - (void)lmn_imageViewDelete:(LMNImageView *)imageView 466 | { 467 | LMNLine *line = imageView.owner; 468 | LMNLine *nextline = line.next; 469 | [imageView removeFromSuperview]; 470 | [imageView unbindFromOwner]; 471 | macro_commitUpdating({ 472 | [_textStorage replaceCharactersInRange:line.range withString:@""]; 473 | [nextline insteadOfLine:line]; 474 | [_textStorage updateNumberingStartWithLine:nextline]; 475 | }); 476 | } 477 | 478 | @end 479 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/UIFont+LMNote.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+LMNote.h 3 | // SimpleWord 4 | // 5 | // Created by littleMeaning on 16/6/30. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIFont (LMNote) 12 | 13 | @property (nonatomic, readonly) BOOL bold; 14 | @property (nonatomic, readonly) BOOL italic; 15 | @property (nonatomic, readonly) float fontSize; 16 | 17 | + (instancetype)fontWithFontSize:(float)fontSize bold:(BOOL)bold italic:(BOOL)italic; 18 | 19 | @end 20 | 21 | -------------------------------------------------------------------------------- /LMNote/LMNote/TextView/UIFont+LMNote.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+LMNote.m 3 | // SimpleWord 4 | // 5 | // Created by littleMeaning on 16/6/30. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "UIFont+LMNote.h" 10 | 11 | @implementation UIFont (LMNote) 12 | 13 | + (instancetype)fontWithFontSize:(float)fontSize bold:(BOOL)bold italic:(BOOL)italic { 14 | 15 | UIFont *font = [UIFont fontWithName:@"PingFang SC" size:fontSize]; 16 | UIFontDescriptor *existingDescriptor = [font fontDescriptor]; 17 | UIFontDescriptorSymbolicTraits traits = existingDescriptor.symbolicTraits; 18 | if (bold) { 19 | traits |= UIFontDescriptorTraitBold; 20 | } 21 | if (italic) { 22 | traits |= UIFontDescriptorTraitItalic; 23 | } 24 | UIFontDescriptor *descriptor = [existingDescriptor fontDescriptorWithSymbolicTraits:traits]; 25 | return [UIFont fontWithDescriptor:descriptor size:fontSize]; 26 | } 27 | 28 | - (BOOL)bold { 29 | return self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold; 30 | } 31 | 32 | - (BOOL)italic { 33 | return self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic; 34 | } 35 | 36 | - (float)fontSize { 37 | return [self.fontDescriptor.fontAttributes[@"NSFontSizeAttribute"] floatValue]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LMNote/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.9 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LMNoteDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 740122602005E4AA0072C387 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7401225F2005E4AA0072C387 /* Assets.xcassets */; }; 11 | 740122632005E4AA0072C387 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 740122612005E4AA0072C387 /* LaunchScreen.storyboard */; }; 12 | 740122662005E4AA0072C387 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 740122652005E4AA0072C387 /* main.m */; }; 13 | 7407769920EBA2930022F99E /* FolderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7407769820EBA2930022F99E /* FolderViewController.m */; }; 14 | 7425942320063B7600E5F309 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7425942220063B7600E5F309 /* AppDelegate.m */; }; 15 | 74264B642063765B0062E669 /* LMNote.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 742594042005E84F00E5F309 /* LMNote.framework */; }; 16 | 74264B652063765B0062E669 /* LMNote.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 742594042005E84F00E5F309 /* LMNote.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 74264B662063765B0062E669 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | 74264B652063765B0062E669 /* LMNote.framework in Embed Frameworks */, 27 | ); 28 | name = "Embed Frameworks"; 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 740122532005E4AA0072C387 /* LMNoteDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LMNoteDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 7401225F2005E4AA0072C387 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 740122622005E4AA0072C387 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 740122642005E4AA0072C387 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 740122652005E4AA0072C387 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 39 | 7407767020E9D0860022F99E /* FolderViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FolderViewController.h; sourceTree = ""; }; 40 | 7407769820EBA2930022F99E /* FolderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FolderViewController.m; sourceTree = ""; }; 41 | 742594042005E84F00E5F309 /* LMNote.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LMNote.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 7425942120063B7600E5F309 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 7425942220063B7600E5F309 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 740122502005E4AA0072C387 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | 74264B642063765B0062E669 /* LMNote.framework in Frameworks */, 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 7401224A2005E4AA0072C387 = { 59 | isa = PBXGroup; 60 | children = ( 61 | 740122552005E4AA0072C387 /* LMNoteDemo */, 62 | 7425942420063B9100E5F309 /* Supporting Files */, 63 | 740122542005E4AA0072C387 /* Products */, 64 | 740122882005E62C0072C387 /* Frameworks */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 740122542005E4AA0072C387 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 740122532005E4AA0072C387 /* LMNoteDemo.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 740122552005E4AA0072C387 /* LMNoteDemo */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 7425942120063B7600E5F309 /* AppDelegate.h */, 80 | 7425942220063B7600E5F309 /* AppDelegate.m */, 81 | 7407767020E9D0860022F99E /* FolderViewController.h */, 82 | 7407769820EBA2930022F99E /* FolderViewController.m */, 83 | ); 84 | path = LMNoteDemo; 85 | sourceTree = ""; 86 | }; 87 | 740122882005E62C0072C387 /* Frameworks */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 742594042005E84F00E5F309 /* LMNote.framework */, 91 | ); 92 | name = Frameworks; 93 | sourceTree = ""; 94 | }; 95 | 7425942420063B9100E5F309 /* Supporting Files */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 7401225F2005E4AA0072C387 /* Assets.xcassets */, 99 | 740122612005E4AA0072C387 /* LaunchScreen.storyboard */, 100 | 740122642005E4AA0072C387 /* Info.plist */, 101 | 740122652005E4AA0072C387 /* main.m */, 102 | ); 103 | path = "Supporting Files"; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 740122522005E4AA0072C387 /* LMNoteDemo */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 740122692005E4AA0072C387 /* Build configuration list for PBXNativeTarget "LMNoteDemo" */; 112 | buildPhases = ( 113 | 7401224F2005E4AA0072C387 /* Sources */, 114 | 740122502005E4AA0072C387 /* Frameworks */, 115 | 740122512005E4AA0072C387 /* Resources */, 116 | 74264B662063765B0062E669 /* Embed Frameworks */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = LMNoteDemo; 123 | productName = LMNoteDemo; 124 | productReference = 740122532005E4AA0072C387 /* LMNoteDemo.app */; 125 | productType = "com.apple.product-type.application"; 126 | }; 127 | /* End PBXNativeTarget section */ 128 | 129 | /* Begin PBXProject section */ 130 | 7401224B2005E4AA0072C387 /* Project object */ = { 131 | isa = PBXProject; 132 | attributes = { 133 | LastUpgradeCheck = 0930; 134 | ORGANIZATIONNAME = littleMeaning; 135 | TargetAttributes = { 136 | 740122522005E4AA0072C387 = { 137 | CreatedOnToolsVersion = 9.1; 138 | ProvisioningStyle = Automatic; 139 | }; 140 | }; 141 | }; 142 | buildConfigurationList = 7401224E2005E4AA0072C387 /* Build configuration list for PBXProject "LMNoteDemo" */; 143 | compatibilityVersion = "Xcode 8.0"; 144 | developmentRegion = en; 145 | hasScannedForEncodings = 0; 146 | knownRegions = ( 147 | en, 148 | Base, 149 | ); 150 | mainGroup = 7401224A2005E4AA0072C387; 151 | productRefGroup = 740122542005E4AA0072C387 /* Products */; 152 | projectDirPath = ""; 153 | projectRoot = ""; 154 | targets = ( 155 | 740122522005E4AA0072C387 /* LMNoteDemo */, 156 | ); 157 | }; 158 | /* End PBXProject section */ 159 | 160 | /* Begin PBXResourcesBuildPhase section */ 161 | 740122512005E4AA0072C387 /* Resources */ = { 162 | isa = PBXResourcesBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | 740122632005E4AA0072C387 /* LaunchScreen.storyboard in Resources */, 166 | 740122602005E4AA0072C387 /* Assets.xcassets in Resources */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXResourcesBuildPhase section */ 171 | 172 | /* Begin PBXSourcesBuildPhase section */ 173 | 7401224F2005E4AA0072C387 /* Sources */ = { 174 | isa = PBXSourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | 7407769920EBA2930022F99E /* FolderViewController.m in Sources */, 178 | 7425942320063B7600E5F309 /* AppDelegate.m in Sources */, 179 | 740122662005E4AA0072C387 /* main.m in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXVariantGroup section */ 186 | 740122612005E4AA0072C387 /* LaunchScreen.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | 740122622005E4AA0072C387 /* Base */, 190 | ); 191 | name = LaunchScreen.storyboard; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXVariantGroup section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 740122672005E4AA0072C387 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 208 | CLANG_WARN_BOOL_CONVERSION = YES; 209 | CLANG_WARN_COMMA = YES; 210 | CLANG_WARN_CONSTANT_CONVERSION = YES; 211 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 213 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INFINITE_RECURSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 220 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 222 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 223 | CLANG_WARN_STRICT_PROTOTYPES = YES; 224 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 225 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 226 | CLANG_WARN_UNREACHABLE_CODE = YES; 227 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 228 | CODE_SIGN_IDENTITY = "iPhone Developer"; 229 | COPY_PHASE_STRIP = NO; 230 | DEBUG_INFORMATION_FORMAT = dwarf; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | ENABLE_TESTABILITY = YES; 233 | GCC_C_LANGUAGE_STANDARD = gnu11; 234 | GCC_DYNAMIC_NO_PIC = NO; 235 | GCC_NO_COMMON_BLOCKS = YES; 236 | GCC_OPTIMIZATION_LEVEL = 0; 237 | GCC_PREPROCESSOR_DEFINITIONS = ( 238 | "DEBUG=1", 239 | "$(inherited)", 240 | ); 241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 243 | GCC_WARN_UNDECLARED_SELECTOR = YES; 244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 245 | GCC_WARN_UNUSED_FUNCTION = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 248 | MTL_ENABLE_DEBUG_INFO = YES; 249 | ONLY_ACTIVE_ARCH = YES; 250 | SDKROOT = iphoneos; 251 | }; 252 | name = Debug; 253 | }; 254 | 740122682005E4AA0072C387 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 280 | CLANG_WARN_STRICT_PROTOTYPES = YES; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | CODE_SIGN_IDENTITY = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu11; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Release; 304 | }; 305 | 7401226A2005E4AA0072C387 /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | CODE_SIGN_IDENTITY = "iPhone Developer"; 310 | CODE_SIGN_STYLE = Automatic; 311 | DEVELOPMENT_TEAM = L99F5K8V47; 312 | INFOPLIST_FILE = "Supporting Files/Info.plist"; 313 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 315 | ONLY_ACTIVE_ARCH = YES; 316 | PRODUCT_BUNDLE_IDENTIFIER = com.lm.LMNoteDemo; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | PROVISIONING_PROFILE_SPECIFIER = ""; 319 | TARGETED_DEVICE_FAMILY = "1,2"; 320 | VALID_ARCHS = "arm64 armv7 armv7s"; 321 | }; 322 | name = Debug; 323 | }; 324 | 7401226B2005E4AA0072C387 /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 328 | CODE_SIGN_IDENTITY = "iPhone Developer"; 329 | CODE_SIGN_STYLE = Automatic; 330 | DEVELOPMENT_TEAM = L99F5K8V47; 331 | INFOPLIST_FILE = "Supporting Files/Info.plist"; 332 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 333 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 334 | PRODUCT_BUNDLE_IDENTIFIER = com.lm.LMNoteDemo; 335 | PRODUCT_NAME = "$(TARGET_NAME)"; 336 | PROVISIONING_PROFILE_SPECIFIER = ""; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | VALID_ARCHS = "arm64 armv7 armv7s"; 339 | }; 340 | name = Release; 341 | }; 342 | /* End XCBuildConfiguration section */ 343 | 344 | /* Begin XCConfigurationList section */ 345 | 7401224E2005E4AA0072C387 /* Build configuration list for PBXProject "LMNoteDemo" */ = { 346 | isa = XCConfigurationList; 347 | buildConfigurations = ( 348 | 740122672005E4AA0072C387 /* Debug */, 349 | 740122682005E4AA0072C387 /* Release */, 350 | ); 351 | defaultConfigurationIsVisible = 0; 352 | defaultConfigurationName = Release; 353 | }; 354 | 740122692005E4AA0072C387 /* Build configuration list for PBXNativeTarget "LMNoteDemo" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | 7401226A2005E4AA0072C387 /* Debug */, 358 | 7401226B2005E4AA0072C387 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | /* End XCConfigurationList section */ 364 | }; 365 | rootObject = 7401224B2005E4AA0072C387 /* Project object */; 366 | } 367 | -------------------------------------------------------------------------------- /LMNoteDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LMNoteDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /LMNoteDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "FolderViewController.h" 11 | 12 | @import LMNote; 13 | 14 | @interface AppDelegate () 15 | 16 | @end 17 | 18 | @implementation AppDelegate 19 | 20 | 21 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 22 | 23 | FolderViewController *rootViewController = [[FolderViewController alloc] init]; 24 | UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:rootViewController]; 25 | 26 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 27 | self.window.rootViewController = navi; 28 | [self.window makeKeyAndVisible]; 29 | return YES; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /LMNoteDemo/FolderViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class LMNFolder; 12 | 13 | @interface FolderViewController : UITableViewController 14 | 15 | @property (nonatomic, strong, readonly) LMNFolder *folder; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LMNoteDemo/FolderViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/7/2. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import "FolderViewController.h" 10 | 11 | @import LMNote; 12 | 13 | @interface FolderViewController () 14 | 15 | @property (nonatomic, strong, readwrite) LMNFolder *folder; 16 | 17 | @end 18 | 19 | @implementation FolderViewController 20 | 21 | - (instancetype)init 22 | { 23 | return [self initWithFolder:nil]; 24 | } 25 | 26 | - (instancetype)initWithFolder:(LMNFolder *)folder 27 | { 28 | self = [super init]; 29 | if (self) { 30 | self.folder = folder ?: [LMNStore shared].rootFolder; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | self.view.backgroundColor = [UIColor whiteColor]; 38 | 39 | UIBarButtonItem *itemAdd = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"baritem_folder"] style:UIBarButtonItemStylePlain target:self action:@selector(add)]; 40 | UIBarButtonItem *itemEdit = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(edit)]; 41 | self.navigationItem.rightBarButtonItems = @[itemEdit, itemAdd]; 42 | 43 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storeChanged:) name:LMNStoreDidChangedNotification object:nil]; 44 | } 45 | 46 | - (void)add 47 | { 48 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"创建目录" message:nil preferredStyle:UIAlertControllerStyleAlert]; 49 | UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 50 | 51 | NSString *name = alert.textFields.firstObject.text; 52 | LMNFolder *folder = [[LMNFolder alloc] initWithUUID:[NSUUID UUID] name:name date:[NSDate date]]; 53 | [self.folder add:folder]; 54 | }]; 55 | UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]; 56 | [alert addAction:confirm]; 57 | [alert addAction:cancel]; 58 | [alert addTextFieldWithConfigurationHandler:nil]; 59 | [self presentViewController:alert animated:YES completion:nil]; 60 | } 61 | 62 | - (void)edit 63 | { 64 | LMNDraft *draft = [[LMNDraft alloc] initWithUUID:[NSUUID UUID] name:@"" date:[NSDate date]]; 65 | draft.parent = self.folder; 66 | LMNoteViewController *vc = [[LMNoteViewController alloc] initWithDraft:draft]; 67 | [self.navigationController pushViewController:vc animated:YES]; 68 | } 69 | 70 | - (void)storeChanged:(NSNotification *)notification 71 | { 72 | [[LMNStore shared] reload]; 73 | self.folder = [LMNStore shared].rootFolder; 74 | 75 | [self.tableView reloadData]; 76 | } 77 | 78 | #pragma mark - Table view data source 79 | 80 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 81 | return self.folder.contents.count; 82 | } 83 | 84 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 85 | 86 | LMNItem *item = self.folder.contents[indexPath.row]; 87 | UITableViewCell *cell; 88 | if ([item isKindOfClass:[LMNFolder class]]) { 89 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 90 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 91 | cell.textLabel.text = item.name; 92 | } 93 | else if ([item isKindOfClass:[LMNDraft class]]) { 94 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; 95 | cell.textLabel.text = item.name; 96 | cell.detailTextLabel.text = item.date.description; 97 | } 98 | return cell; 99 | } 100 | 101 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 102 | { 103 | LMNItem *item = self.folder.contents[indexPath.row]; 104 | if ([item isKindOfClass:[LMNFolder class]]) { 105 | FolderViewController *vc = [[FolderViewController alloc] initWithFolder:(LMNFolder *)item]; 106 | [self.navigationController pushViewController:vc animated:YES]; 107 | } 108 | else if ([item isKindOfClass:[LMNDraft class]]) { 109 | LMNoteViewController *vc = [[LMNoteViewController alloc] initWithDraft:(LMNDraft *)item]; 110 | [self.navigationController pushViewController:vc animated:YES]; 111 | } 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /LMNoteDemo/Util/LMColorPickerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMColorPickerView.h 3 | // LMColorPickerView 4 | // 5 | // Created by Chenly on 16/5/14. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class LMColorPickerView; 12 | 13 | @protocol LMColorPickerViewDataSource 14 | 15 | - (NSInteger)lm_numberOfColorsInColorPickerView:(LMColorPickerView *)pickerView; 16 | - (UIColor *)lm_colorPickerView:(LMColorPickerView *)pickerView colorForItemAtIndex:(NSInteger)index; 17 | 18 | @end 19 | 20 | @protocol LMColorPickerViewDelegate 21 | 22 | @optional 23 | - (void)lm_colorPickerView:(LMColorPickerView *)pickerView didSelectIndex:(NSInteger)index; 24 | - (void)lm_colorPickerView:(LMColorPickerView *)pickerView didSelectColor:(UIColor *)color; 25 | 26 | @end 27 | 28 | @interface LMColorPickerView : UIView 29 | 30 | @property (nonatomic, weak) id dataSource; 31 | @property (nonatomic, weak) id delegate; 32 | 33 | @property (nonatomic, assign) NSInteger spacingBetweenColors; // default is 20.f 34 | 35 | @property (nonatomic, readonly) NSInteger numberOfColors; 36 | @property (nonatomic, readonly) NSInteger selectedIndex; 37 | 38 | - (void)reloadData; 39 | - (void)selectIndex:(NSInteger)index; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /LMNoteDemo/Util/LMColorPickerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMColorPickerView.m 3 | // LMColorPickerView 4 | // 5 | // Created by Chenly on 16/5/14. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "LMColorPickerView.h" 10 | 11 | @interface LMColorPickerView () 12 | 13 | @end 14 | 15 | @implementation LMColorPickerView 16 | { 17 | UIScrollView *_scrollView; 18 | NSMutableArray *_itemViews; 19 | } 20 | 21 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 22 | if (self = [super initWithCoder:aDecoder]) { 23 | [self setup]; 24 | } 25 | return self; 26 | } 27 | 28 | - (instancetype)initWithFrame:(CGRect)frame { 29 | if (self = [super initWithFrame:frame]) { 30 | [self setup]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)setup { 36 | self.backgroundColor = [UIColor whiteColor]; 37 | 38 | _scrollView = [[UIScrollView alloc] init]; 39 | _scrollView.showsHorizontalScrollIndicator = NO; 40 | [self addSubview:_scrollView]; 41 | 42 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 43 | [self addGestureRecognizer:tap]; 44 | 45 | _itemViews = [NSMutableArray array]; 46 | _selectedIndex = -1; 47 | _spacingBetweenColors = 20.f; 48 | } 49 | 50 | - (void)layoutSubviews { 51 | [super layoutSubviews]; 52 | 53 | CGRect rect = CGRectZero; 54 | rect.size.height = (CGRectGetHeight(self.bounds) - self.spacingBetweenColors) / 2; 55 | rect.size.width = rect.size.height; 56 | 57 | NSInteger numberOfColorsInRow = self.numberOfColors / 2 + (self.numberOfColors % 2); 58 | CGFloat contentWidth = numberOfColorsInRow * CGRectGetWidth(rect) + (numberOfColorsInRow - 1) * self.spacingBetweenColors; 59 | if (contentWidth < CGRectGetWidth(self.bounds)) { 60 | // 两行时无法占满宽度,则用一行展示。 61 | rect.origin.y = self.spacingBetweenColors; 62 | rect.size.height = CGRectGetHeight(self.bounds) - self.spacingBetweenColors * 2; 63 | rect.size.width = rect.size.height; 64 | numberOfColorsInRow = self.numberOfColors; 65 | } 66 | 67 | for (NSInteger index = 0; index < _itemViews.count; index++) { 68 | if (index == numberOfColorsInRow) { 69 | rect.origin.x = 0; 70 | rect.origin.y += CGRectGetHeight(rect) + self.spacingBetweenColors; 71 | } 72 | UIView *itemView = _itemViews[index]; 73 | CGAffineTransform transform = itemView.transform; 74 | itemView.transform = CGAffineTransformIdentity; 75 | itemView.frame = rect; 76 | itemView.layer.cornerRadius = CGRectGetWidth(rect) / 2; 77 | itemView.transform = transform; 78 | rect.origin.x += CGRectGetWidth(rect) + self.spacingBetweenColors; 79 | } 80 | CGSize contentSize = CGSizeZero; 81 | contentSize.width = numberOfColorsInRow * CGRectGetWidth(rect) + (numberOfColorsInRow - 1) * self.spacingBetweenColors; 82 | contentSize.height = CGRectGetHeight(self.bounds); 83 | _scrollView.contentSize = contentSize; 84 | _scrollView.frame = self.bounds; 85 | } 86 | 87 | - (void)setDataSource:(id)dataSource { 88 | if (_dataSource == dataSource) { 89 | [self setNeedsLayout]; 90 | return; 91 | } 92 | _dataSource = dataSource; 93 | [self reloadData]; 94 | } 95 | 96 | - (void)reloadData { 97 | [_itemViews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 98 | [_itemViews removeAllObjects]; 99 | _selectedIndex = -1; 100 | if (!self.dataSource) { 101 | return; 102 | } 103 | for (NSInteger index = 0; index < self.numberOfColors; index++) { 104 | UIView *itemView = [[UIView alloc] init]; 105 | itemView.transform = CGAffineTransformMakeScale(0.6, 0.6); 106 | itemView.backgroundColor = [self.dataSource lm_colorPickerView:self colorForItemAtIndex:index]; 107 | [_scrollView addSubview:itemView]; 108 | [_itemViews addObject:itemView]; 109 | } 110 | [self setNeedsLayout]; 111 | } 112 | 113 | - (NSInteger)numberOfColors { 114 | return [self.dataSource lm_numberOfColorsInColorPickerView:self]; 115 | } 116 | 117 | #pragma mark - tap 118 | 119 | - (void)handleTap:(UITapGestureRecognizer *)tap { 120 | CGPoint point = [tap locationInView:_scrollView]; 121 | 122 | for (NSInteger index = 0; index < _itemViews.count; index++) { 123 | UIView *itemView = _itemViews[index]; 124 | if (CGRectContainsPoint(itemView.frame, point)) { 125 | 126 | [self selectIndex:index]; 127 | if ([self.delegate respondsToSelector:@selector(lm_colorPickerView:didSelectIndex:)]) { 128 | [self.delegate lm_colorPickerView:self didSelectIndex:index]; 129 | } 130 | if ([self.delegate respondsToSelector:@selector(lm_colorPickerView:didSelectColor:)]) { 131 | [self.delegate lm_colorPickerView:self didSelectColor:itemView.backgroundColor]; 132 | } 133 | break; 134 | } 135 | } 136 | } 137 | 138 | #pragma mark - 139 | 140 | - (void)selectIndex:(NSInteger)index { 141 | 142 | if (_selectedIndex >= 0 && _selectedIndex < _itemViews.count) { 143 | UIView *lastSelectedView = _itemViews[_selectedIndex]; 144 | lastSelectedView.transform = CGAffineTransformMakeScale(0.6, 0.6); 145 | } 146 | 147 | _selectedIndex = index; 148 | UIView *selectedLabel = _itemViews[_selectedIndex]; 149 | selectedLabel.transform = CGAffineTransformMakeScale(1.f, 1.f); 150 | } 151 | 152 | @end 153 | 154 | -------------------------------------------------------------------------------- /LMNoteDemo/Util/LMTextHTMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMTextHTMLParser.h 3 | // SimpleWord 4 | // 5 | // Created by Chenly on 16/6/27. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LMTextHTMLParser : NSObject 12 | 13 | + (NSString *)HTMLFromAttributedString:(NSAttributedString *)attributedString; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /LMNoteDemo/Util/LMTextHTMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMTextHTMLParser.m 3 | // SimpleWord 4 | // 5 | // Created by Chenly on 16/6/27. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "LMTextHTMLParser.h" 10 | #import "UIFont+LMText.h" 11 | #import "NSTextAttachment+LMText.h" 12 | 13 | @implementation LMTextHTMLParser 14 | 15 | /** 16 | * 将原生的 AttributedString 导出成 HTML,用于 Web 端显示,可以根据自己需求修改导出的 HTML 内容,比如添加 class 或是 id 等。 17 | * 18 | * @param attributedString 需要被导出的富文本 19 | * 20 | * @return 导出的 HTML 21 | */ 22 | + (NSString *)HTMLFromAttributedString:(NSAttributedString *)attributedString { 23 | 24 | BOOL isNewParagraph = YES; 25 | NSMutableString *htmlContent = [NSMutableString string]; 26 | NSRange effectiveRange = NSMakeRange(0, 0); 27 | while (effectiveRange.location + effectiveRange.length < attributedString.length) { 28 | 29 | NSDictionary *attributes = [attributedString attributesAtIndex:effectiveRange.location effectiveRange:&effectiveRange]; 30 | NSTextAttachment *attachment = attributes[@"NSAttachment"]; 31 | if (attachment) { 32 | switch (attachment.attachmentType) { 33 | case LMTextAttachmentTypeImage: 34 | [htmlContent appendString:[NSString stringWithFormat:@"", attachment.userInfo]]; 35 | break; 36 | default: 37 | break; 38 | } 39 | } 40 | else { 41 | NSString *text = [[attributedString string] substringWithRange:effectiveRange]; 42 | UIFont *font = attributes[NSFontAttributeName]; 43 | UIColor *fontColor = attributes[@"NSColor"]; 44 | NSString *color = [self hexStringWithColor:fontColor]; 45 | BOOL underline = [attributes[NSUnderlineStyleAttributeName] boolValue]; 46 | 47 | BOOL isFirst = YES; 48 | 49 | NSArray *components = [text componentsSeparatedByString:@"\n"]; 50 | for (NSInteger i = 0; i < components.count; i ++) { 51 | 52 | NSString *content = components[i]; 53 | 54 | if (!isFirst && !isNewParagraph) { 55 | [htmlContent appendString:@"

"]; 56 | isNewParagraph = YES; 57 | } 58 | if (isNewParagraph && (content.length > 0 || i < components.count - 1)) { 59 | // [htmlContent appendString:[NSString stringWithFormat:@"

", @(2 * paragraphConfig.indentLevel).stringValue]]; 60 | isNewParagraph = NO; 61 | } 62 | [htmlContent appendString:[self HTMLWithContent:content font:font underline:underline color:color]]; 63 | isFirst = NO; 64 | } 65 | if (effectiveRange.location + effectiveRange.length >= attributedString.length && ![htmlContent hasSuffix:@"

"]) { 66 | // 补上

67 | [htmlContent appendString:@"

"]; 68 | } 69 | } 70 | effectiveRange = NSMakeRange(effectiveRange.location + effectiveRange.length, 0); 71 | } 72 | return [htmlContent copy]; 73 | } 74 | 75 | + (NSString *)HTMLWithContent:(NSString *)content font:(UIFont *)font underline:(BOOL)underline color:(NSString *)color { 76 | 77 | if (content.length == 0) { 78 | return @""; 79 | } 80 | 81 | if (font.bold) { 82 | content = [NSString stringWithFormat:@"%@", content]; 83 | } 84 | if (font.italic) { 85 | content = [NSString stringWithFormat:@"%@", content]; 86 | } 87 | if (underline) { 88 | content = [NSString stringWithFormat:@"%@", content]; 89 | } 90 | return [NSString stringWithFormat:@"%@", font.fontSize, color, content]; 91 | } 92 | 93 | + (NSString *)hexStringWithColor:(UIColor *)color { 94 | 95 | NSString *colorString = [[CIColor colorWithCGColor:color.CGColor] stringRepresentation]; 96 | NSArray *parts = [colorString componentsSeparatedByString:@" "]; 97 | 98 | NSMutableString *hexString = [NSMutableString stringWithString:@"#"]; 99 | for (int i = 0; i < 3; i ++) { 100 | [hexString appendString:[NSString stringWithFormat:@"%02X", (int)([parts[i] floatValue] * 255)]]; 101 | } 102 | return [hexString copy]; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /README.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/README.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](./README.gif) 2 | 3 | ## 介绍 4 | LMNote 是原生代码写的富文本编辑器,支持如下功能: 5 | 6 | 1. 粗体、下划线、删除线 7 | 2. 标题、副标题、正文格式 8 | 3. 项目符号、有序列表、检查框段落样式 9 | 4. 居左、居中、居右 10 | 5. 插入图片 11 | 6. 草稿功能 12 | 7. 导出成 HTML 13 | 14 | ### 存在问题 15 | 由于开发和维护时间较少,仅开发了一个 Demo,未做良好封装且存在一些问题(短期内可能不会解决),但基本功能完整而且支持扩展,各位可以依据自己需要直接拿取所需的代码进行二次开发。 16 | 17 | **已知问题:** 18 | 19 | 1. 拼音输入过程中可能会有布局错误 20 | 2. 输入内容中包含 emoji 时布局错误 21 | 3. 图片前后输入文字显示错误 22 | 4. 粘贴或是全选删除可能出现意外情况 23 | 24 | **未完成内容:** 25 | 26 | 1. 斜体 27 | 2. 导出 HTML 上传图片部分代码 28 | 3. 草稿存储图片优化 29 | 30 | -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/baritem_folder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "baritem_folder@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "baritem_folder@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "original" 24 | } 25 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/baritem_folder.imageset/baritem_folder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/baritem_folder.imageset/baritem_folder@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/baritem_folder.imageset/baritem_folder@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/baritem_folder.imageset/baritem_folder@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_bold.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_font_bold@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_font_bold@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_bold.imageset/lmn_font_bold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_bold.imageset/lmn_font_bold@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_bold.imageset/lmn_font_bold@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_bold.imageset/lmn_font_bold@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_italic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_font_italic@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_font_italic@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_italic.imageset/lmn_font_italic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_italic.imageset/lmn_font_italic@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_italic.imageset/lmn_font_italic@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_italic.imageset/lmn_font_italic@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_strikethrough.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_font_strikethrough@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_font_strikethrough@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_strikethrough.imageset/lmn_font_strikethrough@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_strikethrough.imageset/lmn_font_strikethrough@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_strikethrough.imageset/lmn_font_strikethrough@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_strikethrough.imageset/lmn_font_strikethrough@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_underline.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_font_underline@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_font_underline@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_underline.imageset/lmn_font_underline@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_underline.imageset/lmn_font_underline@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/font/lmn_font_underline.imageset/lmn_font_underline@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/font/lmn_font_underline.imageset/lmn_font_underline@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_accessory_checkbox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_accessory_checkbox@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_accessory_checkbox@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_accessory_checkbox.imageset/lmn_accessory_checkbox@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_accessory_checkbox.imageset/lmn_accessory_checkbox@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_accessory_checkbox.imageset/lmn_accessory_checkbox@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_accessory_checkbox.imageset/lmn_accessory_checkbox@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_accessory_checkbox_.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_accessory_checkbox_@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_accessory_checkbox_@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_accessory_checkbox_.imageset/lmn_accessory_checkbox_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_accessory_checkbox_.imageset/lmn_accessory_checkbox_@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_accessory_checkbox_.imageset/lmn_accessory_checkbox_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_accessory_checkbox_.imageset/lmn_accessory_checkbox_@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_center.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_alignment_center@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_alignment_center@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_center.imageset/lmn_alignment_center@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_alignment_center.imageset/lmn_alignment_center@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_center.imageset/lmn_alignment_center@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_alignment_center.imageset/lmn_alignment_center@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_left.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_alignment_left@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_alignment_left@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_left.imageset/lmn_alignment_left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_alignment_left.imageset/lmn_alignment_left@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_left.imageset/lmn_alignment_left@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_alignment_left.imageset/lmn_alignment_left@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_alignment_right@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_alignment_right@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_right.imageset/lmn_alignment_right@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_alignment_right.imageset/lmn_alignment_right@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_alignment_right.imageset/lmn_alignment_right@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_alignment_right.imageset/lmn_alignment_right@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_btn_edit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_btn_edit@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_btn_edit@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_btn_edit.imageset/lmn_btn_edit@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_btn_edit.imageset/lmn_btn_edit@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_btn_edit.imageset/lmn_btn_edit@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_btn_edit.imageset/lmn_btn_edit@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_btn_plus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_btn_plus@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_btn_plus@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_btn_plus.imageset/lmn_btn_plus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_btn_plus.imageset/lmn_btn_plus@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_btn_plus.imageset/lmn_btn_plus@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_btn_plus.imageset/lmn_btn_plus@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_delete@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_delete@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_delete.imageset/lmn_delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_delete.imageset/lmn_delete@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_delete.imageset/lmn_delete@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_delete.imageset/lmn_delete@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_left_square.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_left_square@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_left_square@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_left_square.imageset/lmn_left_square@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_left_square.imageset/lmn_left_square@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_left_square.imageset/lmn_left_square@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_left_square.imageset/lmn_left_square@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_list@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_list@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list.imageset/lmn_list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list.imageset/lmn_list@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list.imageset/lmn_list@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list.imageset/lmn_list@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_checkbox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_list_checkbox@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_list_checkbox@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_checkbox.imageset/lmn_list_checkbox@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list_checkbox.imageset/lmn_list_checkbox@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_checkbox.imageset/lmn_list_checkbox@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list_checkbox.imageset/lmn_list_checkbox@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_dot.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_list_dot@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_list_dot@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_dot.imageset/lmn_list_dot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list_dot.imageset/lmn_list_dot@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_dot.imageset/lmn_list_dot@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list_dot.imageset/lmn_list_dot@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_number.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_list_number@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_list_number@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_number.imageset/lmn_list_number@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list_number.imageset/lmn_list_number@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_list_number.imageset/lmn_list_number@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_list_number.imageset/lmn_list_number@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_a.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_tool_a@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_tool_a@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_a.imageset/lmn_tool_a@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_a.imageset/lmn_tool_a@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_a.imageset/lmn_tool_a@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_a.imageset/lmn_tool_a@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_tool_close@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_tool_close@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_close.imageset/lmn_tool_close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_close.imageset/lmn_tool_close@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_close.imageset/lmn_tool_close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_close.imageset/lmn_tool_close@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_tool_image@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_tool_image@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_image.imageset/lmn_tool_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_image.imageset/lmn_tool_image@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_image.imageset/lmn_tool_image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_image.imageset/lmn_tool_image@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_subtitle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_tool_subtitle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_tool_subtitle@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_subtitle.imageset/lmn_tool_subtitle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_subtitle.imageset/lmn_tool_subtitle@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_subtitle.imageset/lmn_tool_subtitle@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_subtitle.imageset/lmn_tool_subtitle@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_t.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_tool_t@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_tool_t@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_t.imageset/lmn_tool_t@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_t.imageset/lmn_tool_t@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_t.imageset/lmn_tool_t@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_t.imageset/lmn_tool_t@3x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_title.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "lmn_tool_title@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "lmn_tool_title@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_title.imageset/lmn_tool_title@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_title.imageset/lmn_tool_title@2x.png -------------------------------------------------------------------------------- /Supporting Files/Assets.xcassets/lmn_tool_title.imageset/lmn_tool_title@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMNote/db33b9b1caf60d32cf7c58a8ba942754e1139a49/Supporting Files/Assets.xcassets/lmn_tool_title.imageset/lmn_tool_title@3x.png -------------------------------------------------------------------------------- /Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | NSPhotoLibraryUsageDescription 43 | 相册权限 44 | 45 | 46 | -------------------------------------------------------------------------------- /Supporting Files/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LMNoteDemo 4 | // 5 | // Created by littleMeaning on 2018/1/10. 6 | // Copyright © 2018年 littleMeaning. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------