├── .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:@"- %@
", 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 | 
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 |
--------------------------------------------------------------------------------