OSTextView (iOS)

OSTextView.h

#import <QuartzCore/QuartzCore.h>

#define MAXFONTSIZE 256
#define MINFONTSIZE 4
#define FONT_NORMAL             @"Helvetica"
#define FONT_BOLD               @"Helvetica-Bold"
#define FONT_HEADING1           @"Helvetica-Bold"
#define FONT_HEADING2           @"Helvetica-Bold"
#define FONT_HEADING3           @"Helvetica-Bold"
#define FONT_HEADINGDOC         @"Helvetica"
#define FONT_TABLE_HEADING      @"Helvetica-Bold"
#define FONT_TABLE_NORMAL       @"Helvetica"
#define FONT_SIZE_TABLE         10
#define FONT_SIZE_NORMAL        12
#define FONT_SIZE_HEADING1      16
#define FONT_SIZE_HEADING2      14
#define FONT_SIZE_HEADING3      13

@interface OSTextView : UITextView <UITextViewDelegate, UIScrollViewDelegate, NSTextStorageDelegate> {
    
    float fontSizeCount;
    float _originalHeight;
    float _viewWidth;
    float _scale;
    long _firstLineCharsInserted;
}

- (void)sizeText:(id)sender;
- (void)formatText:(id)sender;
- (void)adjustHeight:(float)height;
- (void)restoreHeight;

@end

OSTextView.m

#import "OSTextView.h"
#import "OSTextAttachment.h"

@implementation OSTextView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [self initialize];
    }
    return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if(self){
        // Initialization code
        //FLOG(@"initWithCoder called");
        [self initialize];
    }
    return self;
}
- (void)initialize
{
    //FLOG(@"initialize called");
    
    //Can we set our own LayoutManager here ?
    /*
     OSLayoutManager *layoutManager = [[OSLayoutManager alloc] init];
     [layoutManager addTextContainer:self.textContainer];
     [self.textStorage removeLayoutManager:[self.textStorage.layoutManagers objectAtIndex:0]];
     [self.textStorage addLayoutManager:layoutManager];
     */
    
    self.layer.borderWidth=TEXT_FIELD_BORDER_WIDTH;
    self.layer.borderColor = [[UIColor TEXT_FIELD_BORDER_COLOR] CGColor];
    self.layer.cornerRadius = TEXT_FIELD_BORDER_RADIUS;
    
    UISegmentedControl *_formatControl;
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        _formatControl = [[UISegmentedControl alloc] initWithItems:@[@"H1",@"H2",@"H3",@"N",@"B1",@"B2"]];
    } else  {
        _formatControl = [[UISegmentedControl alloc] initWithItems:@[@"Heading 1",@"Heading 2",@"Heading 3",@"Normal",@"Bullet 1",@"Bullet 2"]];
    }
    
    [_formatControl setMomentary:YES];
    [_formatControl setAlpha:0.85];
    _formatControl.tintColor = [UIColor darkGrayColor];
    float clr = 0.85;
    [_formatControl setBackgroundColor:[UIColor colorWithRed:clr green:clr blue:clr alpha:1.0]];
    
    
    self.inputAccessoryView = _formatControl;
    [_formatControl addTarget:self action:@selector(formatText:) forControlEvents:UIControlEventValueChanged];
    [self.textStorage setDelegate:self];
    
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        _scale = 4.0;
        _viewWidth = 300;
    }
    else {
        _scale = 2.0;
        _viewWidth = 700;
    }
    
}
- (void)formatText:(id)sender {
    //LOG(@"formatText: called");
    UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
    switch ([segmentedControl selectedSegmentIndex]) {
        case 0: //Heading 1
            [self styleHeading1:sender];
            break;
            
        case 1: //Heading 2
            [self styleHeading2:sender];
            break;
            
        case 2: //Heading 3
            [self styleHeading3:sender];
            break;
            
        case 3: //Normal
            [self styleNormal:sender];
            break;
            
        case 4: //Bullet 1
            [self styleBullet1:sender];
            break;
            
        case 5: //Bullet 2
            [self styleBullet2:sender];
            break;
            
        default:
            break;
    }
    
}
/*! Call this method from a UISegmentedControl(smaller|reset|bigger) to change all the font sizes in
 the UITextView.
 
 @param sender   The control sending the message.
 */
- (void)sizeText:(id)sender {
    //LOG(@"sizeText: called");
    UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
    switch ([segmentedControl selectedSegmentIndex]) {
        case 0:
            [self makeTextSmaller:sender];
            break;
            
        case 1:
            [self resetTextSize:sender];
            break;
            
        case 2:
            [self makeTextLarger:sender];
            break;
            
        default:
            break;
    }
    
}
- (void) resetTextSize:(id)sender {
    
    [self resizeText:fontSizeCount];
    
}
- (void) makeTextLarger:(id)sender {
    
    [self resizeText:1.0];
    
}
- (void) makeTextSmaller:(id)sender {
    
    [self resizeText:-1.0];
    
}

/*! This is a method on a UITextView subclass
 
 It will change the point size of all fonts in self.textStorage (self being a UITextView).
 
 It will not make changes if doing so will make any fontSize smaller than MINFONTSIZE, nor will it
 make any changes if doing so will make any font size larger than MAXFONTSIZE
 
 The UITextView has a segmented control on it (Smaller, Reset, Bigger) that floats in the top
 righthand corner.  This is necessary because font sizes straight off a Mac are a bit too small to see properly
 on the iOS devices and I have not figured out how to effectively scale the view any other way without also affecting
 text borders, graphics etc.
 
 @param bySize The size by which to increase or (-)decrease the font size
 */
- (void) resizeText:(float)bySize
{
    //FLOG(@"resizeText called with bySize = %f", bySize);
    NSAttributedString *attrString = self.attributedText;
    NSRange rangeAll = NSMakeRange(0, attrString.length);
    
    
    // First pass is to check the smallest and largest fontSize so we can prevent changes beyond that.
    
    __block float smallestFontSize = 250;
    __block float largestFontSize = 4;
    
    [self.textStorage enumerateAttributesInRange:rangeAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:
     ^(NSDictionary *attributes, NSRange range, BOOL *stop) {
         
         // Iterate over each attribute and look for a Font Size
         [attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
             if ([[key description] isEqualToString:@"NSFont"]) {
                 UIFont *font = obj;
                 float fontSize = font.pointSize + bySize;
                 smallestFontSize = MIN(smallestFontSize, fontSize);
                 largestFontSize = MAX(largestFontSize, fontSize);
             }
             
         }];
     }];
    
    // Don't allow any font to be smaller than MINFONTSIZE
    if (!(smallestFontSize > MINFONTSIZE)) {
        //FLOG(@"  Smallest font is at the minimum size!");
        return;
    }
    
    // Don't allow any font to be greater than MAXFONTSIZE
    if (!(largestFontSize < MAXFONTSIZE)) {
        //FLOG(@"  Largest font is at the maximun size!");
        return;
    }
    
    bool isEditable = self.isEditable;
    if (!isEditable)
        self.editable=YES;
    
    [self.textStorage beginEditing];
    
    [self.textStorage enumerateAttributesInRange:rangeAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:
     ^(NSDictionary *attributes, NSRange range, BOOL *stop) {
         
         NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
         
         // Iterate over each attribute and look for a Font Size
         [mutableAttributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
             
             if ([[key description] isEqualToString:@"NSFont"]) {
                 
                 UIFont *font = obj;
                 float fontSize = font.pointSize;
                 fontSize += bySize;
                 fontSize = MIN(fontSize, MAXFONTSIZE);
                 
                 // Get a new font with the same attributes and the new size
                 UIFont *newFont = [font fontWithSize:fontSize];
                 
                 // Replace the attributes, this overrides whatever is already there
                 [mutableAttributes setObject:newFont forKey:key];
             }
             
         }];
         
         // Now replace the attributes in ourself (UITextView subclass)
         [self.textStorage setAttributes:mutableAttributes range:range];
     }];
    
    [self.textStorage endEditing];
    
    // Now increment/decrement the counter that keeps track of the total size change
    // This is passed when Reset is pushed to reset fonts to their original size.
    self.editable=isEditable;
    
    fontSizeCount = fontSizeCount - bySize;
}

//===========================================
//  Text Formatting
//===========================================
- (NSRange)rangeForUserParagraphAttributeChange {
    NSRange paragaphRange = [self.textStorage.string paragraphRangeForRange: self.selectedRange];
    return paragaphRange;
}
- (IBAction) styleHeading1:(id)sender
{
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    if (charRange.location == NSNotFound) return;
    NSTextStorage *myTextStorage = [self textStorage];
    
    [self setScrollEnabled:NO];
    if ([self isEditable] && charRange.location != NSNotFound)
    {
        [myTextStorage addAttributes:[self heading1Style] range:charRange];
    }
    [self setTypingAttributes:[self heading1Style]];
    [self setScrollEnabled:YES];
}
- (IBAction) styleHeading2:(id)sender
{
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    if (charRange.location == NSNotFound) return;
    NSTextStorage *myTextStorage = [self textStorage];
    
    [self setScrollEnabled:NO];
    if ([self isEditable] && charRange.location != NSNotFound)
    {
        [myTextStorage addAttributes:[self heading2Style] range:charRange];
    }
    [self setTypingAttributes:[self heading2Style]];
    [self setScrollEnabled:YES];
}
- (IBAction) styleHeading3:(id)sender
{
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    if (charRange.location == NSNotFound) return;
    NSTextStorage *myTextStorage = [self textStorage];
    
    [self setScrollEnabled:NO];
    if ([self isEditable] && charRange.location != NSNotFound)
    {
        [myTextStorage addAttributes:[self heading3Style] range:charRange];
    }
    [self setTypingAttributes:[self heading3Style]];
    [self setScrollEnabled:YES];
    
}
- (IBAction) styleBullet1:(id)sender
{
    NSRange selectedRange = [self selectedRange];
    FLOG(@" selectedRange is %u, %u", selectedRange.location, selectedRange.length);
    // Do nothing if no selection
    if (selectedRange.location == NSNotFound) return;
    
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    FLOG(@" initial charRange is %u, %u", charRange.location, charRange.length);
    long initialLength = charRange.length-1;
    FLOG(@" initial length is %ld", initialLength);
    
    NSTextStorage *myTextStorage = [self textStorage];
    
    charRange = [self insertBulletsInSelectedParagraphsIfRequired];
    FLOG(@" modified charRange is %u, %u", charRange.location, charRange.length);
    long finalLength = charRange.length;
    long addedChars = finalLength - initialLength;
    FLOG(@" addedChars is %ld", addedChars);
    
    [self setScrollEnabled:NO];
    if ([self isEditable] && charRange.location != NSNotFound)
    {
        [myTextStorage addAttributes:[self bullet1Style] range:charRange];
    }
    [self setTypingAttributes:[self bullet1Style]];
    
    // Reset the selection to look like it was at the start
    NSRange endRange = NSMakeRange(selectedRange.location+_firstLineCharsInserted, selectedRange.length+addedChars-_firstLineCharsInserted);
    FLOG(@" endRange is %u, %u", charRange.location, charRange.length);
    [self setSelectedRange:endRange];
    [self setScrollEnabled:YES];
    
}
- (IBAction) styleBullet2:(id)sender
{
    NSRange selectedRange = [self selectedRange];
    
    // Do nothing if no selection
    if (selectedRange.location == NSNotFound) return;
    
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    long initialLength = charRange.length-1;
    
    NSTextStorage *myTextStorage = [self textStorage];
    
    charRange = [self insertBulletsInSelectedParagraphsIfRequired];
    long finalLength = charRange.length;
    long addedChars = finalLength - initialLength;
    
    [self setScrollEnabled:NO];
    if ([self isEditable] && charRange.location != NSNotFound)
    {
        [myTextStorage addAttributes:[self bullet2Style] range:charRange];
    }
    [self setTypingAttributes:[self bullet2Style]];
    // Reset the selection to look like it was at the start
    NSRange endRange = NSMakeRange(selectedRange.location+_firstLineCharsInserted, selectedRange.length+addedChars-_firstLineCharsInserted);
    [self setSelectedRange:endRange];
    [self setScrollEnabled:YES];
    
}
/*! Inserts dash+tab character (@"-\t") at the beginning of each paragraph in the
 range returned by [self rangeForUserParagraphAttrubuteChange]. Returns the range extended
 to include any inserted characters.  Checks for the presence of a tab character in the
 paragraphs and if one is present then skips insertion of the dash+tab characters.
 
 @return  Returns the range returned by [self rangeForUserParagraphAttributeChange]
 extended to include any inserted characters
 */
- (NSRange)insertBulletsInSelectedParagraphsIfRequired {
    
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    
    NSUInteger start = charRange.location;
    NSUInteger end = charRange.location+charRange.length;
    
    NSRange startRange = NSMakeRange(start, 0);
    //bool done = NO;
    long numberOfInsertedChars = 0;
    long totalInsertedChars = 0;
    bool firstLine = YES;
    _firstLineCharsInserted = 0;
    
    if (start == end) {
        // Get the first paragraph range
        NSRange paraRange = [self.textStorage.string paragraphRangeForRange:startRange];
        
        //Now insert a bullet if required
        numberOfInsertedChars = [self insertBullet:paraRange];
        if (firstLine) {
            _firstLineCharsInserted = numberOfInsertedChars;
            firstLine = NO;
        }
        totalInsertedChars += numberOfInsertedChars;
        
        // Now get the new ranged (it may have changed if we inserted characters)
        paraRange = [self.textStorage.string paragraphRangeForRange:startRange];
        
        // Also add the number of inserted characters to the "end" value since this would also change
        end += numberOfInsertedChars;
        
    } else {
        while (start < end) {
            
            // Get the first paragraph range
            NSRange paraRange = [self.textStorage.string paragraphRangeForRange:startRange];
            
            //Now insert a bullet if required
            numberOfInsertedChars = [self insertBullet:paraRange];
            if (firstLine) {
                _firstLineCharsInserted = numberOfInsertedChars;
                firstLine = NO;
            }
            totalInsertedChars += numberOfInsertedChars;
            
            // Now get the new ranged (it may have changed if we inserted characters)
            paraRange = [self.textStorage.string paragraphRangeForRange:startRange];
            
            // Also add the number of inserted characters to the "end" value since this would also change
            end += numberOfInsertedChars;
            
            start = paraRange.location + paraRange.length;
            startRange = NSMakeRange(start, 0);
            
        }
    }
    // Now pass back the new range which includes any inserted characters
    NSRange newRange = NSMakeRange(charRange.location, charRange.length+totalInsertedChars-1);
    
    return newRange;
    
}
/*! Checks for existence of (tab) "\t" in string and adds "-\t" if not found.  Returns
 the number of characters that were inserted or 0 if none inserted.
 
 @param range The range of the text to check.
 @return Returns number of characters inserted
 */
- (long)insertBullet:(NSRange)range {
    NSTextStorage *myTextStorage = [self textStorage];
    
    // Check for "\t" in the string and add "-\t" if not found
    NSAttributedString *attrString = [myTextStorage attributedSubstringFromRange:range];
    NSString *string = [attrString string];
    
    if ([string rangeOfString:@"\t"].location == NSNotFound) {
        
        //FLOG(@"string does not contain tab so insert one");
        NSString *tabString = @"-\t";
        NSAttributedString * aStr = [[NSAttributedString alloc] initWithString:tabString];
        
        // Insert a bullet and tab
        [self.textStorage beginEditing];
        [[self textStorage] insertAttributedString:aStr atIndex:range.location];
        [self.textStorage endEditing];
        
        return [tabString length];
        
    } else {
        
        //FLOG(@"string contains tab");
        return 0;
    }
}

/*! Applies the Normal style to the range returned by [self rangeForUserParagraphAttributeChange]
 
 @param sender The id of the control sending the message.
 */
- (IBAction) styleNormal:(id)sender
{
    NSRange charRange = [self rangeForUserParagraphAttributeChange];
    if (charRange.location == NSNotFound) return;
    NSTextStorage *myTextStorage = [self textStorage];
    
    [self setScrollEnabled:NO];
    if ([self isEditable] && charRange.location != NSNotFound)
    {
        [myTextStorage addAttributes:[self normalStyle] range:charRange];
    }
    [self setTypingAttributes:[self normalStyle]];
    [self setScrollEnabled:YES];
    
}

//===========================================
//  Text Styles
//===========================================
/*! Converts from centimeters to points.
 
 @param cm Centimeters to convert
 @return Returns points as a NSNumber
 */
-(NSNumber*)ptsFromCMN:(float)cm
{
    return [NSNumber numberWithFloat:[self ptsFromCMF:cm]];
}
/*! Converts from centimeters to points.
 
 @param  cm Centimeters to convert
 @return Returns points as a float.
 */
-(float)ptsFromCMF:(float)cm
{
    return cm * 28.3464567;
}
/*! Returns a OSSH dark blue color
 
 @return Returns a dark blue UIColor
 */
- (UIColor*)osshDarkBlue
{
    return [self colorRed:OSSH_RED green:OSSH_GREEN blue:OSSH_BLUE];
}
- (UIColor*)colorRed:(int)red green:(int)green blue:(int) blue
{
    return [UIColor colorWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:1.0];
}

/*! Creates a style comprising the following:
 - paragraph style with line indent, first line indent, tail indent, tabs
 - font with font name which includes Bold, Italic or Bold Italic and size
 - color
 - underline style
 
 @param paraStyle The paragraph style
 @param font The font to use
 @param color The font color to use in the style
 @param underlineStyle The underline style to use
 @return Returns a NSDictionary containing the required paragraph attributes
 */
- (NSDictionary*)createStyle:(NSParagraphStyle*)paraStyle font:(UIFont*)font fontColor:(UIColor*)color underlineStyle:(int)underlineStyle
{
    NSMutableDictionary *style = [[NSMutableDictionary alloc] init];
    [style setValue:paraStyle forKey:NSParagraphStyleAttributeName];
    [style setValue:font forKey:NSFontAttributeName];
    [style setValue:color forKey:NSForegroundColorAttributeName];
    [style setValue:[NSNumber numberWithInt: underlineStyle] forKey:NSUnderlineStyleAttributeName];
    
    //FLOG(@" font is %@", font);
    
    return style;
}
- (NSDictionary*)heading1Style
{
    return [self createStyle:[self getHeading1ParagraphStyle] font:[self heading1Font] fontColor:[self osshDarkBlue] underlineStyle:NSUnderlineStyleNone];
    
}
- (NSDictionary*)heading2Style
{
    return [self createStyle:[self getHeading2ParagraphStyle] font:[self heading2Font] fontColor:[self osshDarkBlue] underlineStyle:NSUnderlineStyleNone];
    
}
- (NSDictionary*)heading3Style
{
    return [self createStyle:[self getHeading3ParagraphStyle] font:[self heading3Font] fontColor:[self osshDarkBlue] underlineStyle:NSUnderlineStyleNone];
    
}
- (NSDictionary*)normalStyle
{
    return [self createStyle:[self getDefaultParagraphStyle] font:[self normalFont] fontColor:[UIColor blackColor] underlineStyle:NSUnderlineStyleNone];
    
}
- (NSDictionary*)centeredStyle
{
    return [self createStyle:[self getCenteredParagraphStyle] font:[self normalFont] fontColor:[UIColor blackColor] underlineStyle:NSUnderlineStyleNone];
    
}
- (NSDictionary*)bullet1Style
{
    return [self createStyle:[self getBullet1ParagraphStyle] font:[self normalFont] fontColor:[UIColor blackColor] underlineStyle:NSUnderlineStyleNone];
    
}
- (NSDictionary*)bullet2Style
{
    return [self createStyle:[self getBullet2ParagraphStyle] font:[self normalFont] fontColor:[UIColor blackColor] underlineStyle:NSUnderlineStyleNone];
    
}
- (UIFont*)normalFont
{
    return [UIFont fontWithName:FONT_NORMAL size:FONT_SIZE_NORMAL-fontSizeCount];
}
- (UIFont*)boldFont
{
    return [UIFont fontWithName:FONT_BOLD size:FONT_SIZE_NORMAL-fontSizeCount];
}

- (UIFont*)heading1Font
{
    UIFont *font = [UIFont fontWithName:FONT_HEADING1 size:FONT_SIZE_HEADING1-fontSizeCount];
    return font;
}
- (UIFont*)heading2Font
{
    return [UIFont fontWithName:FONT_HEADING2 size:FONT_SIZE_HEADING2-fontSizeCount];
}
- (UIFont*)heading3Font
{
    return [UIFont fontWithName:FONT_HEADING3 size:FONT_SIZE_HEADING3-fontSizeCount];
}

- (NSParagraphStyle*)getHeading1ParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [self getDefaultParagraphStyle];
    //[para setHeaderLevel:1];
    [para setParagraphSpacing:12];
    return para;
}
- (NSParagraphStyle*)getHeading2ParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [self getDefaultParagraphStyle];
    //[para setHeaderLevel:2];
    [para setParagraphSpacing:9];
    [para setParagraphSpacingBefore:3];
    return para;
}
- (NSParagraphStyle*)getHeading3ParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [self getDefaultParagraphStyle];
    //[para setHeaderLevel:3];
    [para setParagraphSpacing:9];
    [para setParagraphSpacingBefore:3];
    return para;
}
- (NSParagraphStyle*)getHeading4ParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [self getDefaultParagraphStyle];
    //[para setHeaderLevel:4];
    return para;
}
- (NSParagraphStyle*)getBullet1ParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [self getDefaultParagraphStyle];
    NSMutableArray *tabs = [[NSMutableArray alloc] init];
    [tabs addObject:[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:[self ptsFromCMF:1.0] options:nil]];
    //[tabs addObject:[[NSTextTab alloc] initWithType:NSLeftTabStopType location:[self ptsFromCMF:1.0]]];
    [para setTabStops:tabs];
    [para setDefaultTabInterval:[self ptsFromCMF:2.0]];
    [para setFirstLineHeadIndent:[self ptsFromCMF:0.0]];
    //[para setHeaderLevel:0];
    [para setHeadIndent:[self ptsFromCMF:1.0]];
    [para setParagraphSpacing:3];
    [para setParagraphSpacingBefore:3];
    return para;
}
- (NSParagraphStyle*)getBullet2ParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [self getDefaultParagraphStyle];
    NSMutableArray *tabs = [[NSMutableArray alloc] init];
    [tabs addObject:[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:[self ptsFromCMF:1.5] options:nil]];
    //[tabs addObject:[[NSTextTab alloc] initWithType:UITabStopTypeLeft location:[self ptsFromCMF:1.5]]];
    
    [para setTabStops:tabs];
    [para setDefaultTabInterval:[self ptsFromCMF:2.0]];
    [para setFirstLineHeadIndent:[self ptsFromCMF:1.0]];
    //[para setHeaderLevel:0];
    [para setHeadIndent:[self ptsFromCMF:1.5]];
    [para setParagraphSpacing:0];
    [para setParagraphSpacingBefore:0];
    return para;
}
- (NSMutableParagraphStyle*)getRightParagraphStyle
{
    NSMutableParagraphStyle *para = [self getDefaultParagraphStyle];
    [para setAlignment:NSTextAlignmentRight];
    return para;
}
- (NSMutableParagraphStyle*)getCenteredParagraphStyle
{
    NSMutableParagraphStyle *para = [self getDefaultParagraphStyle];
    [para setAlignment:NSTextAlignmentCenter];
    return para;
}
- (NSMutableParagraphStyle*)getDefaultParagraphStyle
{
    NSMutableParagraphStyle *para;
    para = [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
    [para setTabStops:nil];
    [para setAlignment:NSTextAlignmentLeft];
    [para setBaseWritingDirection:NSWritingDirectionLeftToRight];
    [para setDefaultTabInterval:[self ptsFromCMF:3.0]];
    [para setFirstLineHeadIndent:0];
    //[para setHeaderLevel:0];
    [para setHeadIndent:0.0];
    [para setHyphenationFactor:0.0];
    [para setLineBreakMode:NSLineBreakByWordWrapping];
    [para setLineHeightMultiple:1.0];
    [para setLineSpacing:0.0];
    [para setMaximumLineHeight:0];
    [para setMinimumLineHeight:0];
    [para setParagraphSpacing:6];
    [para setParagraphSpacingBefore:3];
    //[para setTabStops:<#(NSArray *)#>];
    [para setTailIndent:0.0];
    return para;
}
- (void)adjustHeight:(float)height {
    return;
    _originalHeight = self.frame.size.height;
    CGRect rect = self.frame;
    rect.size.height=height;
    self.frame=rect;
}
- (void)restoreHeight {
    return;
    CGRect rect = self.frame;
    rect.size.height=_originalHeight;
    self.frame=rect;
}
/*
 - (void)layoutSubviews {
 //FLOG(@"layoutSubviews called");
 [super layoutSubviews];
 _viewWidth = self.textContainer.size.width - 10;
 }
 */
/*! A custom TextStorage delegate method that replaces all the default NSAttachment instances with custom subclass OSTextAttachment
 
 */
- (void)textStorage:(NSTextStorage *)textStorage didProcessEditing:(NSTextStorageEditActions)editedMask range:(NSRange)editedRange changeInLength:(NSInteger)delta {
    //FLOG(@"textStorage:didProcessEditing:range:changeInLength: called");
    __block NSMutableDictionary *dict;
    
    [textStorage enumerateAttributesInRange:editedRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:
     ^(NSDictionary *attributes, NSRange range, BOOL *stop) {
         
         dict = [[NSMutableDictionary alloc] initWithDictionary:attributes];
         
         // Iterate over each attribute and look for attachments
         [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
             
             if ([[key description] isEqualToString:NSAttachmentAttributeName]) {
                 //FLOG(@" textAttachment found");
                 //FLOG(@" textAttachment class is %@", [obj class]);
                 
                 NSTextAttachment *attachment = obj;
                 OSTextAttachment *osAttachment;
                 
                 if (attachment.image) {
                     //FLOG(@" attachment.image found");
                     osAttachment = [[OSTextAttachment alloc] initWithData:UIImagePNGRepresentation(attachment.image) ofType:attachment.fileType];
                 }
                 else {
                     //FLOG(@" attachment.image is nil");
                     osAttachment = [[OSTextAttachment alloc] initWithData:attachment.fileWrapper.regularFileContents ofType:attachment.fileType];
                 }
                 
                 [dict setValue:osAttachment forKey:key];
             }
             
         }];
         
         [textStorage setAttributes:dict range:range];
         
     }];
    
}

- (CGRect)frameOfTextRange:(NSRange)range {
    
    CGRect rect = [self.layoutManager boundingRectForGlyphRange:range inTextContainer:self.textContainer];
    
    // Now convert to textView coordinates
    CGRect rectRange = [self convertRect:rect fromView:self.textInputView];
    
    return rectRange;
}
/*
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 FLOG(@"touchesBegan:withEvent: called");
 
 if (self.selectedRange.location != NSNotFound) {
 FLOG(@" selected location is %d", self.selectedRange.location);
 
 int ch;
 
 if (self.selectedRange.location >= self.textStorage.length) {
 // Get the character at the location
 ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
 } else {
 // Get the character at the location
 ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
 }
 
 if (ch == NSAttachmentCharacter) {
 FLOG(@" selected character is %d, a TextAttachment", ch);
 } else {
 FLOG(@" selected character is %d", ch);
 }
 }
 
 }
 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
 FLOG(@"canPerformAction: called");
 
 FLOG(@" selected location is %d", self.selectedRange.location);
 FLOG(@" TextAttachment character is %d", NSAttachmentCharacter);
 
 if (self.selectedRange.location != NSNotFound) {
 
 int ch;
 
 if (self.selectedRange.location >= self.textStorage.length) {
 // Get the character at the location
 ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
 } else {
 // Get the character at the location
 ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
 }
 
 if (ch == NSAttachmentCharacter) {
 FLOG(@" selected character is %d, a TextAttachment", ch);
 return NO;
 } else {
 FLOG(@" selected character is %d", ch);
 }
 
 // Check for an attachment
 NSTextAttachment *attachment = [[self textStorage] attribute:NSAttachmentAttributeName atIndex:self.selectedRange.location effectiveRange:NULL];
 if (attachment) {
 FLOG(@" attachment attribute retrieved at location %d", self.selectedRange.location);
 return NO;
 }
 else
 FLOG(@" no attachment at location %d", self.selectedRange.location);
 }
 return [super canPerformAction:action withSender:sender];
 }
 */
@end

Leave a comment