mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-21 05:45:13 +00:00
315 lines
12 KiB
Objective-C
Executable file
315 lines
12 KiB
Objective-C
Executable file
//
|
|
// OSKTextView.m
|
|
// Based on JTSTextView by Jared Sinclair
|
|
//
|
|
// Created by Jared Sinclair on 10/26/13.
|
|
// Copyright (c) 2013 Nice Boy LLC. All rights reserved.
|
|
//
|
|
|
|
#import "OSKMicrobloggingTextView.h"
|
|
|
|
#import "OSKLogger.h"
|
|
#import "OSKPresentationManager.h"
|
|
#import "OSKTwitterText.h"
|
|
#import "OSKSmartPunctuation.h"
|
|
#import "OSKTextViewAttachment.h"
|
|
#import "OSKTextViewAttachmentView.h"
|
|
#import "OSKCursorMovement.h"
|
|
#import "UIColor+OSKUtility.h"
|
|
|
|
@interface OSKMicrobloggingTextView ()
|
|
<
|
|
NSTextStorageDelegate,
|
|
OSKTextViewAttachmentViewDelegate
|
|
>
|
|
|
|
@property (strong, nonatomic) NSDictionary *attributes_normal;
|
|
@property (strong, nonatomic) NSDictionary *attributes_mentions;
|
|
@property (strong, nonatomic) NSDictionary *attributes_hashtags;
|
|
@property (strong, nonatomic) NSDictionary *attributes_links;
|
|
@property (strong, nonatomic, readwrite) NSArray *detectedLinks;
|
|
@property (strong, nonatomic) OSKTextViewAttachmentView *attachmentView;
|
|
@property (strong, nonatomic) OSKCursorMovement *cursorMovement;
|
|
|
|
@end
|
|
|
|
@implementation OSKMicrobloggingTextView
|
|
|
|
#pragma mark - UIView
|
|
|
|
- (void)setFrame:(CGRect)frame {
|
|
[super setFrame:frame];
|
|
if (self.attachmentView) {
|
|
[self updateAttachmentViewFrames];
|
|
}
|
|
}
|
|
|
|
#pragma mark - OSKUITextViewSubtitute
|
|
|
|
- (void)commonInit {
|
|
[super commonInit];
|
|
[self.textView.textStorage setDelegate:self];
|
|
self.cursorMovement = [[OSKCursorMovement alloc] initWithTextView:self.textView];
|
|
[self setupAttributes];
|
|
}
|
|
|
|
#pragma mark - Text Storage Delegate
|
|
|
|
- (void)textStorage:(NSTextStorage *)textStorage
|
|
willProcessEditing:(NSTextStorageEditActions)editedMask
|
|
range:(NSRange)editedRange
|
|
changeInLength:(NSInteger)delta {
|
|
|
|
NSInteger lengthChange = [OSKSmartPunctuation fixDumbPunctuation:textStorage editedRange:editedRange textInputObject:self.textView];
|
|
|
|
if (lengthChange != 0) {
|
|
NSRange selectedRange = [self.textView selectedRange];
|
|
selectedRange.location += lengthChange;
|
|
[self.textView setSelectedRange:selectedRange];
|
|
}
|
|
|
|
[self updateSyntaxHighlighting:textStorage];
|
|
}
|
|
|
|
#pragma mark - Syntax Highlighting
|
|
|
|
- (void)setupAttributes {
|
|
OSKPresentationManager *manager = [OSKPresentationManager sharedInstance];
|
|
|
|
CGFloat fontSize = [manager textViewFontSize];
|
|
|
|
UIFont *normalFont = nil;
|
|
UIFont *boldFont = nil;
|
|
UIFontDescriptor *normalDescriptor = [manager normalFontDescriptor];
|
|
UIFontDescriptor *boldDescriptor = [manager boldFontDescriptor];
|
|
|
|
if (normalDescriptor) {
|
|
normalFont = [UIFont fontWithDescriptor:normalDescriptor size:fontSize];
|
|
} else {
|
|
normalFont = [UIFont systemFontOfSize:fontSize];
|
|
}
|
|
|
|
if (boldDescriptor) {
|
|
boldFont = [UIFont fontWithDescriptor:boldDescriptor size:fontSize];
|
|
} else {
|
|
boldFont = [UIFont boldSystemFontOfSize:fontSize];
|
|
}
|
|
|
|
UIColor *normalColor = manager.color_text;
|
|
UIColor *actionColor = manager.color_action;
|
|
UIColor *hashtagColor = [UIColor colorWithWhite:0.5 alpha:1.0];
|
|
|
|
_attributes_normal = @{NSFontAttributeName:normalFont,
|
|
NSForegroundColorAttributeName:normalColor};
|
|
|
|
_attributes_mentions = @{NSFontAttributeName:boldFont,
|
|
NSForegroundColorAttributeName:actionColor};
|
|
|
|
_attributes_hashtags = @{NSFontAttributeName:normalFont,
|
|
NSForegroundColorAttributeName:hashtagColor};
|
|
|
|
_attributes_links = @{NSFontAttributeName:normalFont,
|
|
NSForegroundColorAttributeName:actionColor};
|
|
|
|
[self.textView setTypingAttributes:_attributes_normal];
|
|
|
|
[self setTintColor:actionColor];
|
|
[self.textView setTintColor:actionColor];
|
|
[self setBackgroundColor:manager.color_textViewBackground];
|
|
[self.textView setBackgroundColor:manager.color_textViewBackground];
|
|
|
|
UIKeyboardAppearance keyboardAppearance;
|
|
if (manager.sheetStyle == OSKActivitySheetViewControllerStyle_Dark) {
|
|
keyboardAppearance = UIKeyboardAppearanceAlert;
|
|
} else {
|
|
keyboardAppearance = UIKeyboardAppearanceLight;
|
|
}
|
|
[self.textView setKeyboardAppearance:keyboardAppearance];
|
|
[self.textView setKeyboardType:UIKeyboardTypeTwitter];
|
|
|
|
[self.textView setAttributedText:[[NSAttributedString alloc] initWithString:@"" attributes:_attributes_normal]];
|
|
}
|
|
|
|
- (void)updateSyntaxHighlighting:(NSTextStorage *)textStorage {
|
|
|
|
// This method could have poor performance for very long runs of text.
|
|
// Consider refactoring to only inspect the region around the edited range. ~ JTS March 21, 2014.
|
|
|
|
// Apply default attributes to the entire string
|
|
[textStorage addAttributes:self.attributes_normal range:NSMakeRange(0, textStorage.length)];
|
|
|
|
if (self.syntaxHighlighting == OSKSyntaxHighlighting_None) {
|
|
[self setDetectedLinks:nil];
|
|
} else {
|
|
BOOL useLinks = (self.syntaxHighlighting & OSKSyntaxHighlighting_Links);
|
|
BOOL useUsernames = (self.syntaxHighlighting & OSKSyntaxHighlighting_Usernames);
|
|
BOOL useHashtags = (self.syntaxHighlighting & OSKSyntaxHighlighting_Hashtags);
|
|
NSArray *allEntities = [OSKTwitterText entitiesInText:textStorage.string];
|
|
NSMutableArray *links = [[NSMutableArray alloc] init];
|
|
for (OSKTwitterTextEntity *anEntity in allEntities) {
|
|
switch (anEntity.type) {
|
|
case OSKTwitterTextEntityHashtag: {
|
|
if (useHashtags) {
|
|
[textStorage addAttributes:self.attributes_hashtags range:anEntity.range];
|
|
}
|
|
} break;
|
|
case OSKTwitterTextEntityScreenName: {
|
|
if (useUsernames) {
|
|
NSString *lowercaseName = [textStorage.string substringWithRange:anEntity.range].lowercaseString;
|
|
[textStorage replaceCharactersInRange:anEntity.range withString:lowercaseName];
|
|
[textStorage addAttributes:self.attributes_mentions range:anEntity.range];
|
|
}
|
|
} break;
|
|
case OSKTwitterTextEntityURL: {
|
|
if (useLinks) {
|
|
[textStorage addAttributes:self.attributes_links range:anEntity.range];
|
|
[links addObject:anEntity];
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
[self setDetectedLinks:links];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Text Attachments
|
|
|
|
- (void)setOskAttachment:(OSKTextViewAttachment *)attachment {
|
|
if (self.attachmentView) {
|
|
[self.attachmentView removeFromSuperview];
|
|
[self setAttachmentView:nil];
|
|
}
|
|
_oskAttachment = attachment;
|
|
if (_oskAttachment) {
|
|
[self setupAttachmentView:attachment];
|
|
}
|
|
}
|
|
|
|
- (void)setupAttachmentView:(OSKTextViewAttachment *)newAttachment {
|
|
CGFloat width;
|
|
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
|
|
width = OSKTextViewAttachmentViewWidth_Phone;
|
|
} else {
|
|
width = OSKTextViewAttachmentViewWidth_Pad;
|
|
}
|
|
|
|
CGSize thumbSize = CGSizeMake(width, width);
|
|
CGSize sizeNeeded = [OSKTextViewAttachment sizeNeededForThumbs:newAttachment.images.count ofIndividualSize:thumbSize];
|
|
CGRect startFrame = CGRectMake(0, 0, sizeNeeded.width, sizeNeeded.height);
|
|
|
|
OSKTextViewAttachmentView *attachmentView = [OSKTextViewAttachmentView buttonWithType:UIButtonTypeCustom];
|
|
[attachmentView setFrame:startFrame];
|
|
[attachmentView setAttachment:newAttachment];
|
|
[attachmentView setDelegate:self];
|
|
attachmentView.autoresizingMask = UIViewAutoresizingNone;
|
|
attachmentView.backgroundColor = [UIColor clearColor];
|
|
[self.textView addSubview:attachmentView];
|
|
[self setAttachmentView:attachmentView];
|
|
|
|
if ([self.oskAttachmentsDelegate respondsToSelector:@selector(textViewShouldUseBorderedAttachmentView:)]) {
|
|
BOOL useBorders = [self.oskAttachmentsDelegate textViewShouldUseBorderedAttachmentView:self];
|
|
if (useBorders) {
|
|
OSKActivitySheetViewControllerStyle sheetStyle = [OSKPresentationManager sharedInstance].sheetStyle;
|
|
UIColor *contrastingColor = (sheetStyle == OSKActivitySheetViewControllerStyle_Dark)
|
|
? [UIColor colorWithWhite:1 alpha:0.2]
|
|
: [UIColor colorWithWhite:0 alpha:0.2];
|
|
CGFloat borderWidth = ([UIScreen mainScreen].scale > 1) ? 0.5f : 1.0f;
|
|
self.attachmentView.layer.borderWidth = borderWidth;
|
|
self.attachmentView.layer.borderColor = contrastingColor.CGColor;
|
|
}
|
|
}
|
|
|
|
if ([self.oskAttachmentsDelegate textView:self shouldAllowAttachmentsToBeEdited:newAttachment]) {
|
|
[self.attachmentView setUserInteractionEnabled:YES];
|
|
} else {
|
|
[self.attachmentView setUserInteractionEnabled:NO];
|
|
}
|
|
|
|
[self updateAttachmentViewFrames];
|
|
}
|
|
|
|
- (void)updateAttachmentViewFrames {
|
|
CGFloat width;
|
|
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
|
|
width = OSKTextViewAttachmentViewWidth_Phone;
|
|
} else {
|
|
width = OSKTextViewAttachmentViewWidth_Pad;
|
|
}
|
|
NSUInteger numberOfImages = self.attachmentView.attachment.images.count;
|
|
CGFloat myWidth = self.textView.frame.size.width;
|
|
CGFloat padding = (numberOfImages > 1) ? 14.0f : 8.0f;
|
|
CGFloat viewWidth = width;
|
|
CGFloat viewHeight = viewWidth;
|
|
CGFloat xOrigin = myWidth - padding - viewWidth;
|
|
CGFloat yOrigin = (numberOfImages > 1) ? 14.0f : 10.0f;
|
|
CGFloat centerY = yOrigin + viewHeight/2.0f;
|
|
CGFloat centerX = xOrigin + viewWidth/2.0f;
|
|
CGPoint center = CGPointMake(centerX, centerY);
|
|
|
|
[_attachmentView setCenter:center];
|
|
|
|
CGRect frame = CGRectMake(xOrigin, yOrigin, viewWidth, viewHeight);
|
|
UIBezierPath *path = [self exclusionPathForRect:frame desiredInnerPadding:padding];
|
|
[self.textView.textContainer setExclusionPaths:@[path]];
|
|
}
|
|
|
|
- (UIBezierPath *)exclusionPathForRect:(CGRect)rect
|
|
desiredInnerPadding:(CGFloat)padding {
|
|
CGRect adjustedRect = rect;
|
|
adjustedRect.origin.x -= padding;
|
|
adjustedRect.origin.y = 0.0;
|
|
adjustedRect.size.height = rect.origin.y + rect.size.height;
|
|
adjustedRect.size.width = self.textView.frame.size.width - adjustedRect.origin.x;
|
|
return [UIBezierPath bezierPathWithRect:adjustedRect];
|
|
}
|
|
|
|
- (void)removeAttachment {
|
|
[self.attachmentView removeFromSuperview];
|
|
[self setAttachmentView:nil];
|
|
[self setOskAttachment:nil];
|
|
[self.textView.textContainer setExclusionPaths:nil];
|
|
}
|
|
|
|
#pragma mark - OSKTextViewAttachmentViewDelegate
|
|
|
|
- (void)attachmentViewDidTapRemove:(OSKTextViewAttachmentView *)view {
|
|
[view resignFirstResponder];
|
|
[self becomeFirstResponder];
|
|
[self.oskAttachmentsDelegate textViewDidTapRemoveAttachment:self];
|
|
}
|
|
|
|
- (BOOL)attachmentViewShouldReportHasText:(OSKTextViewAttachmentView *)view {
|
|
return [self.textView hasText];
|
|
}
|
|
|
|
- (void)attachmentView:(OSKTextViewAttachmentView *)view
|
|
didInsertText:(NSString *)text {
|
|
[view resignFirstResponder];
|
|
[self becomeFirstResponder];
|
|
[self insertText:text];
|
|
}
|
|
|
|
- (void)attachmentViewDidDeleteBackward:(OSKTextViewAttachmentView *)view {
|
|
[view resignFirstResponder];
|
|
[self becomeFirstResponder];
|
|
[self.textView deleteBackward];
|
|
}
|
|
|
|
- (UIKeyboardAppearance)attachmentViewKeyboardAppearance:(OSKTextViewAttachmentView *)view {
|
|
return self.textView.keyboardAppearance;
|
|
}
|
|
|
|
- (UIKeyboardType)attachmentViewKeyboardType:(OSKTextViewAttachmentView *)view {
|
|
return self.textView.keyboardType;
|
|
}
|
|
|
|
- (UIReturnKeyType)attachmentViewReturnKeyType:(OSKTextViewAttachmentView *)view {
|
|
return self.textView.returnKeyType;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|