Restore the input toolbar's placeholder text.

// FREEBIE
pull/1/head
Matthew Chen 8 years ago
parent 7d3df0bf0a
commit 4a94d039e8

@ -18,10 +18,22 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - #pragma mark -
@protocol ConversationTextViewToolbarDelegate <NSObject>
- (void)textViewDidChange;
- (void)textViewReturnPressed;
@end
#pragma mark -
@interface ConversationInputTextView : UITextView @interface ConversationInputTextView : UITextView
@property (weak, nonatomic) id<ConversationInputTextViewDelegate> inputTextViewDelegate; @property (weak, nonatomic) id<ConversationInputTextViewDelegate> inputTextViewDelegate;
@property (weak, nonatomic) id<ConversationTextViewToolbarDelegate> textViewToolbarDelegate;
- (NSString *)trimmedText; - (NSString *)trimmedText;
@end @end

@ -8,6 +8,16 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@interface ConversationInputTextView () <UITextViewDelegate>
@property (nonatomic) UILabel *placeholderView;
@property (nonatomic) NSArray<NSLayoutConstraint *> *placeholderConstraints;
@property (nonatomic) BOOL isEditing;
@end
#pragma mark -
@implementation ConversationInputTextView @implementation ConversationInputTextView
- (instancetype)init - (instancetype)init
@ -16,9 +26,10 @@ NS_ASSUME_NONNULL_BEGIN
if (self) { if (self) {
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; [self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.delegate = self;
CGFloat cornerRadius = 6.0f; CGFloat cornerRadius = 6.0f;
self.font = [UIFont ows_dynamicTypeBodyFont];
self.backgroundColor = [UIColor whiteColor]; self.backgroundColor = [UIColor whiteColor];
self.layer.borderColor = [UIColor lightGrayColor].CGColor; self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 0.5f; self.layer.borderWidth = 0.5f;
@ -26,9 +37,6 @@ NS_ASSUME_NONNULL_BEGIN
self.scrollIndicatorInsets = UIEdgeInsetsMake(cornerRadius, 0.0f, cornerRadius, 0.0f); self.scrollIndicatorInsets = UIEdgeInsetsMake(cornerRadius, 0.0f, cornerRadius, 0.0f);
self.textContainerInset = UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f);
self.contentInset = UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f);
self.scrollEnabled = YES; self.scrollEnabled = YES;
self.scrollsToTop = NO; self.scrollsToTop = NO;
self.userInteractionEnabled = YES; self.userInteractionEnabled = YES;
@ -45,13 +53,89 @@ NS_ASSUME_NONNULL_BEGIN
self.text = nil; self.text = nil;
// _placeHolder = nil; self.placeholderView = [UILabel new];
// _placeHolderTextColor = [UIColor lightGrayColor]; self.placeholderView.text = NSLocalizedString(@"new_message", @"");
self.placeholderView.textColor = [UIColor lightGrayColor];
self.placeholderView.textAlignment = NSTextAlignmentLeft;
[self addSubview:self.placeholderView];
// We need to do these steps _after_ placeholderView is configured.
self.font = [UIFont ows_dynamicTypeBodyFont];
self.textContainerInset = UIEdgeInsetsMake(4.0f, 2.0f, 4.0f, 2.0f);
self.contentInset = UIEdgeInsetsMake(1.0f, 0.0f, 1.0f, 0.0f);
[self ensurePlaceholderConstraints];
[self updatePlaceholderVisibility];
} }
return self; return self;
} }
- (void)setFont:(UIFont *_Nullable)font
{
[super setFont:font];
self.placeholderView.font = font;
}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
[super setContentInset:contentInset];
[self ensurePlaceholderConstraints];
}
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
{
[super setTextContainerInset:textContainerInset];
[self ensurePlaceholderConstraints];
}
- (void)ensurePlaceholderConstraints
{
OWSAssert(self.placeholderView);
if (self.placeholderConstraints) {
[NSLayoutConstraint deactivateConstraints:self.placeholderConstraints];
}
// We align the location of our placeholder with the text content of
// this view. The only safe way to do that is by measuring the
// beginning position.
UITextRange *beginningTextRange =
[self textRangeFromPosition:self.beginningOfDocument toPosition:self.beginningOfDocument];
CGRect beginningTextRect = [self firstRectForRange:beginningTextRange];
CGFloat hInset = beginningTextRect.origin.x;
CGFloat topInset = beginningTextRect.origin.y;
self.placeholderConstraints = @[
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:hInset],
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:hInset],
[self.placeholderView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topInset],
];
}
- (void)updatePlaceholderVisibility
{
self.placeholderView.hidden = self.text.length > 0 || self.isEditing;
}
- (void)setText:(NSString *_Nullable)text
{
[super setText:text];
[self updatePlaceholderVisibility];
}
- (void)setIsEditing:(BOOL)isEditing
{
_isEditing = isEditing;
[self updatePlaceholderVisibility];
}
- (BOOL)canBecomeFirstResponder - (BOOL)canBecomeFirstResponder
{ {
return YES; return YES;
@ -246,6 +330,47 @@ NS_ASSUME_NONNULL_BEGIN
//} //}
//@end //@end
#pragma mark - UITextViewDelegate
- (void)textViewDidBeginEditing:(UITextView *)textView
{
// TODO: Is this necessary?
[textView becomeFirstResponder];
self.isEditing = YES;
}
- (void)textViewDidChange:(UITextView *)textView
{
OWSAssert(self.textViewToolbarDelegate);
[self updatePlaceholderVisibility];
[self.textViewToolbarDelegate textViewDidChange];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
[textView resignFirstResponder];
self.isEditing = NO;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
OWSAssert(self.textViewToolbarDelegate);
if (range.length > 0) {
return YES;
}
if ([text isEqualToString:@"\n"]) {
[self.textViewToolbarDelegate textViewReturnPressed];
return NO;
}
return YES;
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -20,9 +20,6 @@ NS_ASSUME_NONNULL_BEGIN
- (void)textViewDidChange; - (void)textViewDidChange;
// TODO: Is this necessary.
//- (void)textViewDidBeginEditing;
@end @end
#pragma mark - #pragma mark -

@ -14,25 +14,26 @@ NS_ASSUME_NONNULL_BEGIN
static void *kConversationInputTextViewObservingContext = &kConversationInputTextViewObservingContext; static void *kConversationInputTextViewObservingContext = &kConversationInputTextViewObservingContext;
@interface ConversationInputToolbar () <UIGestureRecognizerDelegate, UITextViewDelegate> @interface ConversationInputToolbar () <UIGestureRecognizerDelegate, ConversationTextViewToolbarDelegate>
@property (nonatomic, readonly) ConversationInputTextView *inputTextView;
@property (nonatomic, readonly) UIButton *attachmentButton;
@property (nonatomic, readonly) UIButton *sendButton;
@property (nonatomic, readonly) UIButton *voiceMemoButton;
@property (nonatomic, readonly) UIView *leftButtonWrapper;
@property (nonatomic, readonly) UIView *rightButtonWrapper;
@property (nonatomic) ConversationInputTextView *inputTextView;
@property (nonatomic) UIButton *attachmentButton;
@property (nonatomic) UIButton *sendButton;
@property (nonatomic) BOOL shouldShowVoiceMemoButton; @property (nonatomic) BOOL shouldShowVoiceMemoButton;
@property (nonatomic) UIButton *voiceMemoButton;
@property (nonatomic) UIView *leftButtonWrapper;
@property (nonatomic) UIView *rightButtonWrapper;
@property (nonatomic) NSArray<NSLayoutConstraint *> *contentContraints; @property (nonatomic) NSArray<NSLayoutConstraint *> *contentContraints;
#pragma mark - Voice Memo Recording UI #pragma mark - Voice Memo Recording UI
@property (nonatomic, nullable) UIView *voiceMemoUI; @property (nonatomic, nullable) UIView *voiceMemoUI;
@property (nonatomic) UIView *voiceMemoContentView; @property (nonatomic, nullable) UIView *voiceMemoContentView;
@property (nonatomic) NSDate *voiceMemoStartTime; @property (nonatomic) NSDate *voiceMemoStartTime;
@property (nonatomic, nullable) NSTimer *voiceMemoUpdateTimer; @property (nonatomic, nullable) NSTimer *voiceMemoUpdateTimer;
@property (nonatomic) UILabel *recordingLabel; @property (nonatomic, nullable) UILabel *recordingLabel;
@property (nonatomic) BOOL isRecordingVoiceMemo; @property (nonatomic) BOOL isRecordingVoiceMemo;
@property (nonatomic) CGPoint voiceMemoGestureStartLocation; @property (nonatomic) CGPoint voiceMemoGestureStartLocation;
@ -69,7 +70,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex
[backgroundView autoPinEdgesToSuperviewEdges]; [backgroundView autoPinEdgesToSuperviewEdges];
_inputTextView = [ConversationInputTextView new]; _inputTextView = [ConversationInputTextView new];
self.inputTextView.delegate = self; self.inputTextView.textViewToolbarDelegate = self;
[self addSubview:self.inputTextView]; [self addSubview:self.inputTextView];
// We want to be permissive about taps on the send and attachment buttons, // We want to be permissive about taps on the send and attachment buttons,
@ -111,7 +112,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex
UIImage *voiceMemoIcon = [UIImage imageNamed:@"voice-memo-button"]; UIImage *voiceMemoIcon = [UIImage imageNamed:@"voice-memo-button"];
OWSAssert(voiceMemoIcon); OWSAssert(voiceMemoIcon);
self.voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom]; _voiceMemoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.voiceMemoButton setImage:[voiceMemoIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] [self.voiceMemoButton setImage:[voiceMemoIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
self.voiceMemoButton.imageView.tintColor = [UIColor ows_materialBlueColor]; self.voiceMemoButton.imageView.tintColor = [UIColor ows_materialBlueColor];
@ -157,7 +158,7 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex
[self ensureShouldShowVoiceMemoButton]; [self ensureShouldShowVoiceMemoButton];
// TODO: Remove this when we remove the delegate method. // TODO: Remove this when we remove the delegate method.
[self textViewDidChange:self.inputTextView]; [self textViewDidChange];
} }
- (void)clearTextMessage - (void)clearTextMessage
@ -495,6 +496,8 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex
UIView *oldVoiceMemoUI = self.voiceMemoUI; UIView *oldVoiceMemoUI = self.voiceMemoUI;
self.voiceMemoUI = nil; self.voiceMemoUI = nil;
self.voiceMemoContentView = nil;
self.recordingLabel = nil;
NSTimer *voiceMemoUpdateTimer = self.voiceMemoUpdateTimer; NSTimer *voiceMemoUpdateTimer = self.voiceMemoUpdateTimer;
self.voiceMemoUpdateTimer = nil; self.voiceMemoUpdateTimer = nil;
@ -572,41 +575,19 @@ static void *kConversationInputTextViewObservingContext = &kConversationInputTex
[self.inputToolbarDelegate attachmentButtonPressed]; [self.inputToolbarDelegate attachmentButtonPressed];
} }
#pragma mark - UITextViewDelegate #pragma mark - ConversationTextViewToolbarDelegate
- (void)textViewDidBeginEditing:(UITextView *)textView
{
OWSAssert(textView == self.inputTextView);
[textView becomeFirstResponder];
}
- (void)textViewDidChange:(UITextView *)textView - (void)textViewDidChange
{ {
OWSAssert(self.inputToolbarDelegate); OWSAssert(self.inputToolbarDelegate);
OWSAssert(textView == self.inputTextView);
[self ensureShouldShowVoiceMemoButton]; [self ensureShouldShowVoiceMemoButton];
[self.inputToolbarDelegate textViewDidChange]; [self.inputToolbarDelegate textViewDidChange];
} }
- (void)textViewDidEndEditing:(UITextView *)textView - (void)textViewReturnPressed
{
OWSAssert(textView == self.inputTextView);
[textView resignFirstResponder];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{ {
if (range.length > 0) { [self sendButtonPressed];
return YES;
}
if ([text isEqualToString:@"\n"]) {
[self sendButtonPressed];
return NO;
}
return YES;
} }
#pragma mark - Text Input Sizing #pragma mark - Text Input Sizing

@ -3491,6 +3491,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
message:errorMessage]; message:errorMessage];
} }
// TODO: Is this necessary? It seems redundant with observing changes to
// the collection view's layout.
- (void)textViewDidChangeLayout - (void)textViewDidChangeLayout
{ {
OWSAssert([NSThread isMainThread]); OWSAssert([NSThread isMainThread]);

Loading…
Cancel
Save