Created
March 4, 2014 02:40
-
-
Save keicoder/9339324 to your computer and use it in GitHub Desktop.
objective-c : Textkit and TableView with note (ios 7)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Textkit and TableView with note (ios 7) | |
//1. start with basic dummy data | |
//AppDelegate.h | |
@interface AppDelegate : UIResponder <UIApplicationDelegate> | |
@property (strong, nonatomic) UIWindow *window; | |
@property NSMutableArray* notes; | |
@end | |
//AppDelegate.m | |
#import "Note.h" | |
@implementation AppDelegate | |
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions | |
{ | |
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));} | |
// create some notes! | |
self.notes = [NSMutableArray arrayWithArray: @[ | |
[Note noteWithText:@"Shopping List\r\r1. Cheese\r2. Biscuits\r3. Sausages\r4. IMPORTANT Cash for going out!\r5. -potatoes-\r6. A copy of iOS6 by tutorials\r7. A new iPhone\r8. A present for mum"], | |
[Note noteWithText:@"Meeting notes\rA long and drawn out meeting, it lasted hours and hours and hours!"], | |
[Note noteWithText:@"Perfection ... \n\nPerfection is achieved not when there is nothing left to add, but when there is nothing left to take away - Antoine de Saint-Exupery"], | |
[Note noteWithText:@"Notes on iOS7\nThis is a big change in the UI design, it's going to take a *lot* of getting used to!"], | |
[Note noteWithText:@"Meeting notes\rA dfferent meeting, just as long and boring"], | |
[Note noteWithText:@"A collection of thoughts\rWhy do birds sing? Why is the sky blue? Why is it so hard to create good test data?"]]]; | |
return YES; | |
} | |
//Note.h | |
@interface Note : NSObject | |
@property NSString* contents; | |
@property NSDate* timestamp; | |
// an automatically generated not title, based on the first few words | |
@property (readonly) NSString* title; | |
+ (Note*) noteWithText:(NSString*)text; | |
@end | |
//Note.m | |
#import "Note.h" | |
@implementation Note | |
+ (Note *)noteWithText:(NSString *)text { | |
Note* note = [Note new]; | |
note.contents = text; | |
note.timestamp = [NSDate date]; | |
return note; | |
} | |
- (NSString *)title { | |
// split into lines | |
NSArray* lines = [self.contents componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; | |
// return the first | |
return lines[0]; | |
} | |
@end | |
//NotesListViewController.h | |
#import <UIKit/UIKit.h> | |
@interface NotesListViewController : UITableViewController | |
@end | |
//NotesListViewController.m | |
#import "NotesListViewController.h" | |
#import "AppDelegate.h" | |
#import "Note.h" | |
@implementation NotesListViewController | |
- (NSMutableArray*) notes | |
{ | |
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));} | |
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; | |
return appDelegate.notes; | |
} | |
- (void)viewDidAppear:(BOOL)animated { | |
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));} | |
// Whenever this view controller appears, reload the table. This allows it to reflect any changes | |
// made whilst editing notes. | |
[self.tableView reloadData]; | |
[super viewDidAppear:animated]; | |
} | |
- (void)viewDidLoad | |
{ | |
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));} | |
[super viewDidLoad]; | |
} | |
#pragma mark - Table view data source | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section | |
{ | |
return [self notes].count; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath | |
{ | |
static NSString *CellIdentifier = @"Cell"; | |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; | |
Note* note = [self notes][indexPath.row]; | |
cell.textLabel.text = note.title; | |
return cell; | |
} | |
@end | |
//2. transfering data between views | |
//NoteEditorViewController.h | |
@class Note; | |
@interface NoteEditorViewController : UIViewController | |
@property Note* note; | |
@end | |
//NoteEditorViewController.m | |
#import "NoteEditorViewController.h" | |
#import "Note.h" | |
@interface NoteEditorViewController () <UITextViewDelegate> | |
@property (weak, nonatomic) IBOutlet UITextView *textView; | |
@end | |
@implementation NoteEditorViewController | |
- (void)viewDidLoad | |
{ | |
[super viewDidLoad]; | |
self.textView.text = self.note.contents; | |
self.textView.delegate = self; | |
} | |
#pragma mark - UITextView 델리게이트 메소드 | |
- (void)textViewDidEndEditing:(UITextView *)textView //tells the delegate that editing of the specified text view has ended | |
{ | |
// copy the updated note text to the underlying model. | |
self.note.contents = textView.text; | |
} | |
@end | |
//transfering data from tableview to detailview | |
//before implementing prepareForSegue 메소드 | |
//hook up with TextView, in NotesListViewController add bar button to segue identifier "AddNewNote" and prototype cell to segue identifier "CellSelected" | |
//NotesListViewController.m | |
#import "NoteEditorViewController.h" | |
#pragma mark - Navigation | |
// In a story board-based application, you will often want to do a little preparation before navigation | |
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender | |
{ | |
NoteEditorViewController* editorVC = (NoteEditorViewController*)segue.destinationViewController; | |
if ([segue.identifier isEqualToString:@"CellSelected"]) { //CellSelected : for edit | |
// if the cell selected segue was fired, edit the selected note | |
NSIndexPath *path = [self.tableView indexPathForSelectedRow]; | |
editorVC.note = [self notes][path.row]; | |
} | |
if ([segue.identifier isEqualToString:@"AddNewNote"]) { //AddNewNote : for new note | |
// if the add new note segue was fired, create a new note, and edit it | |
editorVC.note = [Note noteWithText:@" "]; | |
// also, add this note to the collection | |
[[self notes] addObject:editorVC.note]; | |
} | |
} | |
//3. Dynamic type | |
//for starter implementing Basic support | |
//NoteEditorViewController.m | |
//add the following code to the end of method | |
- (void)viewDidLoad | |
{ | |
self.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; | |
// handle content size change notifications | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(preferredContentSizeChanged:) | |
name:UIContentSizeCategoryDidChangeNotification | |
object:nil]; | |
} | |
- (void)preferredContentSizeChanged:(NSNotification *)n { | |
self.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; | |
} | |
//NotesListViewController.m | |
//add the following code to the end of method | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath | |
{ | |
cell.textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; | |
} | |
- (void)viewDidLoad | |
{ | |
// handle content size change notifications | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(preferredContentSizeChanged:) | |
name:UIContentSizeCategoryDidChangeNotification | |
object:nil]; | |
} | |
- (void)preferredContentSizeChanged:(NSNotification *)n { | |
[self.tableView reloadData]; | |
} | |
//change cell's height follow dynamic type font size | |
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { | |
// size the row based on the size required to render a label. | |
static UILabel* label; | |
if (!label) { | |
label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, FLT_MAX, FLT_MAX)]; | |
label.text = @"test"; | |
} | |
label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; | |
[label sizeToFit]; | |
return label.frame.size.height * 1.7; | |
} | |
//4. Letterpress effects | |
//NotesListViewController.m | |
//add folling code | |
#pragma mark - Table view data source | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section | |
{ | |
return [self notes].count; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath | |
{ | |
//... | |
//creates an attributed string for the title of a table cell using the letterpress style | |
UIColor* textColor = [UIColor colorWithRed:0.175f green:0.458f blue:0.831f alpha:1.0f]; | |
NSDictionary *attrs = @{NSForegroundColorAttributeName : textColor, | |
NSFontAttributeName : font, | |
NSTextEffectAttributeName : NSTextEffectLetterpressStyle}; | |
NSAttributedString* attrString = [[NSAttributedString alloc] initWithString:note.title | |
attributes:attrs]; | |
cell.textLabel.attributedText = attrString; | |
return cell; | |
} | |
//5. Apply exclusion paths (flowing text around images or other objects) | |
//Adding the view | |
//NoteEditorViewController.m | |
#import "TimeIndicatorView.h" | |
//TimeIndicatorView.h | |
// a view that renders the time of a note within a circle | |
@interface TimeIndicatorView : UIView | |
- (id)init:(NSDate*)date; | |
// updates the size of this view to comfortably hold the current text. After this | |
// method is invoked the view will be located at the origin. | |
- (void)updateSize; | |
- (UIBezierPath *)curvePathWithOrigin:(CGPoint)origin; | |
@end | |
//TimeIndicatorView.m | |
#import "TimeIndicatorView.h" | |
@implementation TimeIndicatorView | |
{ | |
UILabel* _label; | |
} | |
-(id)init:(NSDate *)date { | |
if (self = [super init]) { | |
// Initialization code | |
self.backgroundColor = [UIColor clearColor]; | |
self.clipsToBounds = NO; | |
_label = [[UILabel alloc] init]; | |
_label.textAlignment = NSTextAlignmentRight; | |
// format and style the date | |
NSDateFormatter* format = [NSDateFormatter new]; | |
[format setDateFormat:@"dd\rMMMM\ryyyy"]; | |
NSString* formattedDate = [format stringFromDate:date]; | |
_label.text = [formattedDate uppercaseString]; | |
_label.textAlignment = NSTextAlignmentCenter; | |
_label.textColor = [UIColor whiteColor]; | |
_label.numberOfLines = 0; | |
[self addSubview:_label]; | |
} | |
return self; | |
} | |
- (void)updateSize { | |
// size the label based on the font | |
_label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; | |
_label.frame = CGRectMake(0, 0, FLT_MAX, FLT_MAX); | |
[_label sizeToFit]; | |
// set the frame to be large enough to accomodate circle that surrounds the text | |
float radius = [self radiusToSurroundFrame:_label.frame]; | |
self.frame = CGRectMake(0, 0, radius*2, radius*2); | |
// center the label within this circle | |
_label.center = self.center; | |
// offset the center of this view to ... erm ... can I just draw you a picture? | |
// You know the story - the designer provides a mock-up with some static data, leaving | |
// you to work out the complex calculatins required to accomodate the variability of real-world | |
// data. C'est la vie! | |
float padding = 5.0f; | |
self.center = CGPointMake(self.center.x + _label.frame.origin.x - padding, | |
self.center.y - _label.frame.origin.y + padding); | |
} | |
// calculates the radius of the circle that surrounds the label | |
- (float) radiusToSurroundFrame:(CGRect)frame { | |
return MAX(frame.size.width, frame.size.height) * 0.5 + 20.0f; | |
} | |
- (UIBezierPath *)curvePathWithOrigin:(CGPoint)origin { | |
return [UIBezierPath bezierPathWithArcCenter:origin | |
radius:[self radiusToSurroundFrame:_label.frame] | |
startAngle:-180.0f | |
endAngle:180.0f | |
clockwise:YES]; | |
} | |
- (void)drawRect:(CGRect)rect | |
{ | |
CGContextRef ctx = UIGraphicsGetCurrentContext(); | |
CGContextSetShouldAntialias(ctx, YES); | |
UIBezierPath* path = [self curvePathWithOrigin:_label.center]; | |
[[UIColor colorWithRed:0.329f green:0.584f blue:0.898f alpha:1.0f] setFill]; | |
[path fill]; | |
} | |
@end | |
//NoteEditorViewController.m | |
//add the following instance variable | |
@implementation NoteEditorViewController | |
{ | |
TimeIndicatorView* _timeView; | |
} | |
- (void)viewDidLoad | |
{ | |
//... | |
_timeView = [[TimeIndicatorView alloc] init:self.note.timestamp]; | |
[self.view addSubview:_timeView]; | |
} | |
- (void)viewDidLayoutSubviews { //viewDidLayoutSubviews: called to notify the view controller that its view has just laid out its subviews. | |
[self updateTimeIndicatorFrame]; | |
} | |
//call updateSize to make TimeIndicatorView calculates its own size automatically | |
- (void) updateTimeIndicatorFrame { | |
[_timeView updateSize]; | |
_timeView.frame = CGRectOffset(_timeView.frame, | |
self.view.frame.size.width - _timeView.frame.size.width, 0.0); //frame, x, y | |
} | |
//add following code | |
//when view controller receives notification that the size of the content has changed | |
- (void)preferredContentSizeChanged:(NSNotification *)n { | |
//... | |
[self updateTimeIndicatorFrame]; | |
} | |
//TimeIndicatorView.h | |
//add the following method declaration: | |
- (UIBezierPath *)curvePathWithOrigin:(CGPoint)origin; | |
//NoteEditorViewController.m | |
//add the following code to the end of updateTimeIndicatorFrame: | |
/*below method creates an exclusion path based on the Bezier path created in your time indicator view, but with an origin and coordinates that are relative to the text view*/ | |
- (void) updateTimeIndicatorFrame { | |
//... | |
//add an exclusion path for the time display | |
UIBezierPath* exclusionPath = [_timeView curvePathWithOrigin:_timeView.center]; | |
_textView.textContainer.exclusionPaths = @[exclusionPath]; | |
} | |
//6. Dynamic text formatting and storage | |
//make subclass of NSTextStorage | |
//SyntaxHighlightTextStorage.h | |
// a text storage subclass that performs syntax highlighting | |
@interface SyntaxHighlightTextStorage : NSTextStorage | |
@end | |
//SyntaxHighlightTextStorage.m | |
@implementation SyntaxHighlightTextStorage | |
{ | |
//NSMutableAttributedString cause text storage subclass must provide its own ‘persistence’ | |
NSMutableAttributedString *_backingStore; | |
} | |
- (id)init | |
{ | |
if (self = [super init]) { | |
_backingStore = [NSMutableAttributedString new]; | |
} | |
return self; | |
} | |
#pragma mark - NSTextStorage mandatory overrides | |
- (NSString *)string | |
{ | |
return [_backingStore string]; | |
} | |
- (NSDictionary *)attributesAtIndex:(NSUInteger)location | |
effectiveRange:(NSRangePointer)range | |
{ | |
return [_backingStore attributesAtIndex:location | |
effectiveRange:range]; | |
} | |
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str | |
{ | |
NSLog(@"replaceCharactersInRange:%@ withString:%@", | |
NSStringFromRange(range), str); | |
[self beginEditing]; | |
[_backingStore replaceCharactersInRange:range withString:str]; | |
[self edited:NSTextStorageEditedCharacters | NSTextStorageEditedAttributes | |
range:range changeInLength:str.length - range.length]; | |
[self endEditing]; | |
} | |
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range | |
{ | |
NSLog(@"setAttributes:%@ range:%@", | |
attrs, NSStringFromRange(range)); | |
[self beginEditing]; | |
[_backingStore setAttributes:attrs range:range]; | |
[self edited:NSTextStorageEditedAttributes | |
range:range changeInLength:0]; | |
[self endEditing]; | |
} | |
//7. UITextView with a custom Text Kit stack | |
//create the UITextView and Text Kit stack programatically | |
//delete UITextView instance in storyboard, delete UITextView outlet in NoteEditorViewController.m | |
//NoteEditorControllerViewController.m | |
@implementation NoteEditorViewController | |
{ | |
//... | |
SyntaxHighlightTextStorage* _textStorage; | |
UITextView* _textView; | |
} | |
//remove following lines from viewDidLoad in NoteEditorViewController.m | |
- (void)viewDidLoad | |
{ | |
//... | |
//remove following code | |
self.textView.text = self.note.contents; | |
self.textView.delegate = self; | |
self.textView.font = [UIFont | |
preferredFontForTextStyle:UIFontTextStyleBody]; | |
} | |
//create TextView programatically | |
//NoteEditorViewController.m | |
- (void)createTextView | |
{ | |
// 1. Create the text storage that backs the editor | |
NSDictionary* attrs = @{NSFontAttributeName: | |
[UIFont preferredFontForTextStyle:UIFontTextStyleBody]}; | |
NSAttributedString* attrString = [[NSAttributedString alloc] initWithString:_note.contents | |
attributes:attrs]; | |
_textStorage = [SyntaxHighlightTextStorage new]; | |
[_textStorage appendAttributedString:attrString]; | |
CGRect newTextViewRect = self.view.bounds; | |
// 2. Create the layout manager | |
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; | |
// 3. Create a text container | |
CGSize containerSize = CGSizeMake(newTextViewRect.size.width, CGFLOAT_MAX); | |
NSTextContainer *container = [[NSTextContainer alloc] initWithSize:containerSize]; | |
container.widthTracksTextView = YES; | |
[layoutManager addTextContainer:container]; | |
[_textStorage addLayoutManager:layoutManager]; | |
// 4. Create a UITextView | |
_textView = [[UITextView alloc] initWithFrame:newTextViewRect | |
textContainer:container]; | |
_textView.delegate = self; | |
[self.view addSubview:_textView]; | |
// ensure that the text view is not editable initially | |
_textView.editable = NO; | |
_textView.dataDetectorTypes = UIDataDetectorTypeLink; //clickable URLs in the text view | |
} | |
//call createTextView method | |
- (void)viewDidLoad | |
{ | |
//... | |
[self createTextView]; | |
} | |
//replace the old outlet property with the new instance variable | |
- (void)preferredContentSizeChanged:(NSNotification *)n { | |
_textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; | |
} | |
- (void)viewDidLayoutSubviews { //called to notify the view controller that view has just laid out subviews | |
//... | |
_textView.frame = _textViewFrame; | |
//custom view created in code doesn’t automatically inherit the layout constraints set in the storyboard | |
//therefore, the frame of your new view won’t resize when the device orientation changes | |
//need to explicitly set the frame yourself | |
} | |
//8. Dynamic formatting | |
//SyntaxHighlightTextStorage.m | |
-(void)processEditing { //Triggers post-editing operations | |
[self performReplacementsForRange:[self editedRange]]; | |
[super processEditing]; | |
} | |
- (void)performReplacementsForRange:(NSRange)changedRange { | |
//expands the range because changedRange typically indicates a single character | |
//lineRangeForRange extends that range to the entire line of text | |
NSRange extendedRange = NSUnionRange(changedRange, [[_backingStore string] lineRangeForRange:NSMakeRange(changedRange.location, 0)]); | |
extendedRange = NSUnionRange(extendedRange, [[_backingStore string] lineRangeForRange:NSMakeRange(NSMaxRange(changedRange), 0)]); | |
[self applyStylesToRange:extendedRange]; | |
} | |
- (void)applyStylesToRange:(NSRange)searchRange { | |
// 1. create some fonts | |
UIFontDescriptor* fontDescriptor = [UIFontDescriptor | |
preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; | |
UIFontDescriptor* boldFontDescriptor = [fontDescriptor | |
fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; | |
UIFont* boldFont = [UIFont fontWithDescriptor:boldFontDescriptor size: 0.0]; | |
UIFont* normalFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; | |
// 2. match items surrounded by asterisks | |
NSString* regexStr = @"(\\*\\w+(\\s\\w+)*\\*)\\s"; | |
NSRegularExpression* regex = [NSRegularExpression | |
regularExpressionWithPattern:regexStr | |
options:0 | |
error:nil]; | |
NSDictionary* boldAttributes = @{ NSFontAttributeName : boldFont }; | |
NSDictionary* normalAttributes = @{ NSFontAttributeName : normalFont }; | |
// 3. iterate over each match, making the text bold | |
[regex enumerateMatchesInString:[_backingStore string] options:0 range:searchRange usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){ | |
NSRange matchRange = [match range]; | |
[self addAttributes:boldAttributes range:matchRange]; | |
// 4. reset the style to the original | |
if (NSMaxRange(matchRange)+1 < self.length) { | |
[self addAttributes:normalAttributes range:NSMakeRange(NSMaxRange(matchRange)+1, 1)]; | |
} | |
}]; | |
} | |
//9. Adding further styles | |
//basic principle of applying styles: | |
//*** use a regex to find and replace the string using applyStylesToRange | |
//Add an instance variable to | |
//SyntaxHighlightTextStorage.m | |
@implementation SyntaxHighlightTextStorage | |
{ | |
//... | |
NSDictionary* _replacements; | |
} | |
- (void) createHighlightPatterns { | |
UIFontDescriptor *scriptFontDescriptor = [UIFontDescriptor | |
fontDescriptorWithFontAttributes: | |
@{UIFontDescriptorFamilyAttribute: @"Zapfino"}]; | |
// 1. base our script font on the preferred body font size | |
UIFontDescriptor* bodyFontDescriptor = [UIFontDescriptor | |
preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; | |
NSNumber* bodyFontSize = bodyFontDescriptor.fontAttributes[UIFontDescriptorSizeAttribute]; | |
UIFont* scriptFont = [UIFont fontWithDescriptor:scriptFontDescriptor | |
size:[bodyFontSize floatValue]]; | |
// 2. create the attributes | |
NSDictionary* boldAttributes = [self | |
createAttributesForFontStyle:UIFontTextStyleBody | |
withTrait:UIFontDescriptorTraitBold]; | |
NSDictionary* italicAttributes = [self | |
createAttributesForFontStyle:UIFontTextStyleBody | |
withTrait:UIFontDescriptorTraitItalic]; | |
NSDictionary* strikeThroughAttributes = @{ NSStrikethroughStyleAttributeName : @1}; | |
NSDictionary* scriptAttributes = @{ NSFontAttributeName : scriptFont}; | |
NSDictionary* redTextAttributes = @{ NSForegroundColorAttributeName : [UIColor redColor]}; | |
// construct a dictionary of replacements based on regexes | |
_replacements = @{ | |
@"(\\*\\w+(\\s\\w+)*\\*)\\s" : boldAttributes, | |
@"(_\\w+(\\s\\w+)*_)\\s" : italicAttributes, | |
@"([0-9]+\\.)\\s" : boldAttributes, | |
@"(-\\w+(\\s\\w+)*-)\\s" : strikeThroughAttributes, | |
@"(~\\w+(\\s\\w+)*~)\\s" : scriptAttributes, | |
@"\\s([A-Z]{2,})\\s" : redTextAttributes}; | |
} | |
/* | |
deconstruct the regular expression | |
1. (\* - match an asterisk | |
2. \w+ - followed by one or more “word” characters | |
3. (\s\w+)* - followed by zero or more groups of spaces followed by “word” | |
characters | |
4. \*) - followed by an asterisk | |
5. \s - terminated by a space | |
http://www.raywenderlich.com/30288/nsregularexpression-tutorial-and-cheat-sheet | |
*/ | |
//actually call createHighlightPatterns. Update init | |
- (id)init { | |
if (self = [super init]) { | |
//... | |
[self createHighlightPatterns]; | |
} | |
return self; | |
} | |
//Add the following method | |
//below method applies the supplied font style to the body font. | |
//provides a zero size to return a size that matches the user’s current font size preferences | |
- (NSDictionary*)createAttributesForFontStyle:(NSString*)style | |
withTrait:(uint32_t)trait { | |
UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; | |
UIFontDescriptor *descriptorWithTrait = [fontDescriptor fontDescriptorWithSymbolicTraits:trait]; | |
UIFont* font = [UIFont fontWithDescriptor: descriptorWithTrait | |
size: 0.0]; | |
return @{ NSFontAttributeName : font }; | |
} | |
//replace the existing applyStylesToRange method | |
//below method code does pretty much exactly what it did before | |
//but this time it iterates over the dictionary of regex matches and attributes | |
//and applies the specified style to the matched patterns | |
- (void)applyStylesToRange:(NSRange)searchRange | |
{ | |
NSDictionary* normalAttrs = @{NSFontAttributeName: | |
[UIFont preferredFontForTextStyle:UIFontTextStyleBody]}; | |
// iterate over each replacement | |
for (NSString* key in _replacements) { | |
NSRegularExpression *regex = [NSRegularExpression | |
regularExpressionWithPattern:key | |
options:0 | |
error:nil]; | |
NSDictionary* attributes = _replacements[key]; | |
[regex enumerateMatchesInString:[_backingStore string] | |
options:0 | |
range:searchRange | |
usingBlock:^(NSTextCheckingResult *match, | |
NSMatchingFlags flags, | |
BOOL *stop){ | |
// apply the style | |
NSRange matchRange = [match range]; | |
[self addAttributes:attributes range:matchRange]; | |
// reset the style to the original | |
if (NSMaxRange(matchRange)+1 < self.length) { | |
[self addAttributes:normalAttrs | |
range:NSMakeRange(NSMaxRange(matchRange)+1, 1)]; | |
} | |
}]; | |
} | |
} | |
//10. Reviving dynamic type | |
//Problem : | |
//1. changed the orientation of your screen, app no longer responds to changing content size | |
//2. add a lot of text to a note, bottom of the text view is partially obscured by the keyboard | |
//SyntaxHighlightTextStorage.h | |
//add the following method declaration to the interface | |
@interface SyntaxHighlightTextStorage : NSTextStorage | |
- (void)update; | |
@end | |
//SyntaxHighlightTextStorage.m | |
-(void)update { | |
// update the highlight patterns | |
[self createHighlightPatterns]; | |
// change the 'global' font | |
NSDictionary* bodyFont = @{NSFontAttributeName : | |
[UIFont preferredFontForTextStyle:UIFontTextStyleBody]}; | |
[self addAttributes : bodyFont | |
range : NSMakeRange(0, self.length)]; | |
// re-apply the regex matches | |
[self applyStylesToRange:NSMakeRange(0, self.length)]; | |
} | |
//NoteEditorViewController.m | |
//update preferredContentSizeChanged: to invoke update: | |
- (void)preferredContentSizeChanged:(NSNotification *)n { | |
//... | |
[_textStorage update]; | |
} | |
//11. Resizing text views | |
//Add the following instance variable to NoteEditorViewController.m | |
@implementation NoteEditorViewController | |
{ | |
//... | |
CGRect _textViewFrame; //used to store the current text view frame | |
} | |
//update viewDidLayoutSubviews to make use of this newly added instance variable | |
- (void)viewDidLayoutSubviews | |
{ | |
//called to notify the view controller that view has just laid out subviews | |
//... | |
_textView.frame = _textViewFrame; | |
} | |
//add the following to the bottom of viewDidLoad to set the initial frame value | |
- (void)viewDidLoad | |
{ | |
//... | |
_textViewFrame = self.view.bounds; | |
//... | |
} | |
//update _textViewFrame variable when the keyboard is shown | |
#pragma mark - UITextView 델리게이트 메소드 | |
- (void)textViewDidBeginEditing:(UITextView *)textView | |
{ | |
//텍스트 뷰 사이즈 조정 | |
[UIView animateWithDuration:0.3 animations:^{ | |
_textViewFrame = self.view.bounds; | |
_textViewFrame.size.height -= 216.0f; //reduces the height of the on-screen keyboard | |
_textView.frame = _textViewFrame; | |
}]; | |
} | |
//return the text view frame to its original size when editing finishes | |
- (void)textViewDidEndEditing:(UITextView *)textView | |
{ | |
//copy the updated note text to the underlying model | |
self.note.contents = textView.text; | |
_textViewFrame = self.view.bounds; | |
_textView.frame = _textViewFrame; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment