From efa726b515d98a8bc7128bc1bc761f8ee1a559c5 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 26 Oct 2021 16:27:51 +0300 Subject: [PATCH 1/2] vector-im/element-ios/issues/4976 - Replaced GrowingTextView with simpler, custom implementation. Cleaned up the RoomInputToolbar header. --- Podfile | 1 - .../RoomInputToolbarTextView.swift | 115 ++++++++++++++++-- .../Views/InputToolbar/RoomInputToolbarView.h | 44 +++---- .../Views/InputToolbar/RoomInputToolbarView.m | 98 +++++++-------- changelog.d/4976.change | 1 + 5 files changed, 172 insertions(+), 87 deletions(-) create mode 100644 changelog.d/4976.change diff --git a/Podfile b/Podfile index dec1aaefba..f2fab251b8 100644 --- a/Podfile +++ b/Podfile @@ -73,7 +73,6 @@ abstract_target 'RiotPods' do pod 'SideMenu', '~> 6.5' pod 'DSWaveformImage', '~> 6.1.1' pod 'ffmpeg-kit-ios-audio', '~> 4.5' - pod 'GrowingTextView', '~> 0.7.2' pod 'FLEX', '~> 4.5.0', :configurations => ['Debug'] diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift index 729b3c2e7a..4e8916e541 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift @@ -14,26 +14,91 @@ // limitations under the License. // -import GrowingTextView - @objc protocol RoomInputToolbarTextViewDelegate: AnyObject { + func textView(_ textView: RoomInputToolbarTextView, didChangeHeight height: CGFloat) func textView(_ textView: RoomInputToolbarTextView, didReceivePasteForMediaFromSender sender: Any?) } -class RoomInputToolbarTextView: GrowingTextView { +@objcMembers +class RoomInputToolbarTextView: UITextView { - @objc weak var toolbarDelegate: RoomInputToolbarTextViewDelegate? + private var heightConstraint: NSLayoutConstraint! + + weak var toolbarDelegate: RoomInputToolbarTextViewDelegate? + + var placeholder: String? + var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) - override var keyCommands: [UIKeyCommand]? { - return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))] + var minHeight: CGFloat = 30.0 { + didSet { + updateUI() + } } - @objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) { - guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else { + var maxHeight: CGFloat = 0.0 { + didSet { + updateUI() + } + } + + override var text: String! { + didSet { + updateUI() + } + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { + contentMode = .redraw + + NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: UITextView.textDidChangeNotification, object: self) + + if let heightConstraint = constraints.filter({ $0.firstAttribute == .height && $0.relation == .equal }).first { + self.heightConstraint = heightConstraint + } else { + heightConstraint = self.heightAnchor.constraint(equalToConstant: minHeight) + addConstraint(heightConstraint) + } + } + + // MARK: - Overrides + + override func layoutSubviews() { + super.layoutSubviews() + updateUI() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + + guard text.isEmpty, let placeholder = placeholder else { return } - delegate.onTouchUp(inside: delegate.rightInputToolbarButton) + var attributes: [NSAttributedString.Key: Any] = [.foregroundColor: placeholderColor] + if let font = font { + attributes[.font] = font + } + + let frame = rect.inset(by: .init(top: textContainerInset.top, + left: textContainerInset.left + textContainer.lineFragmentPadding, + bottom: textContainerInset.bottom, + right: textContainerInset.right)) + + placeholder.draw(in: frame, withAttributes: attributes) + } + + override var keyCommands: [UIKeyCommand]? { + return [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(keyCommandSelector(_:)))] } /// Overrides paste to handle images pasted from Safari, passing them up to the input toolbar. @@ -49,4 +114,36 @@ class RoomInputToolbarTextView: GrowingTextView { super.paste(sender) } } + + // MARK: - Private + + @objc private func textDidChange(notification: Notification) { + if let sender = notification.object as? RoomInputToolbarTextView, sender == self { + updateUI() + } + } + + private func updateUI() { + var height = sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)).height + height = minHeight > 0 ? max(height, minHeight) : height + height = maxHeight > 0 ? min(height, maxHeight) : height + + // Update placeholder + self.setNeedsDisplay() + + guard height != heightConstraint.constant else { + return + } + + heightConstraint.constant = height + toolbarDelegate?.textView(self, didChangeHeight: height) + } + + @objc private func keyCommandSelector(_ keyCommand: UIKeyCommand) { + guard keyCommand.input == "\r", let delegate = (self.delegate as? RoomInputToolbarView) else { + return + } + + delegate.onTouchUp(inside: delegate.rightInputToolbarButton) + } } diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 3099fd50f5..d126ba22f0 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -60,28 +60,10 @@ typedef enum : NSUInteger */ @property (nonatomic, weak) id delegate; -@property (weak, nonatomic) IBOutlet UIView *mainToolbarView; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint; - -@property (weak, nonatomic) IBOutlet UIButton *attachMediaButton; - -@property (weak, nonatomic) IBOutlet UIImageView *inputTextBackgroundView; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint; -@property (weak, nonatomic) IBOutlet UIImageView *inputContextImageView; -@property (weak, nonatomic) IBOutlet UILabel *inputContextLabel; -@property (weak, nonatomic) IBOutlet UIButton *inputContextButton; -@property (weak, nonatomic) IBOutlet RoomActionsBar *actionsBar; -@property (weak, nonatomic) UIView *voiceMessageToolbarView; - /** Tell whether the filled data will be sent encrypted. NO by default. */ -@property (nonatomic) BOOL isEncryptionEnabled; +@property (nonatomic, assign) BOOL isEncryptionEnabled; /** Sender of the event being edited / replied. @@ -91,11 +73,31 @@ typedef enum : NSUInteger /** Destination of the message in the composer. */ -@property (nonatomic) RoomInputToolbarViewSendMode sendMode; +@property (nonatomic, assign) RoomInputToolbarViewSendMode sendMode; /** YES if action menu is opened. NO otherwise */ -@property (nonatomic, getter=isActionMenuOpened) BOOL actionMenuOpened; +@property (nonatomic, assign) BOOL actionMenuOpened; + +/** + The input toolbar's main height constraint + */ +@property (nonatomic, weak, readonly) NSLayoutConstraint *mainToolbarHeightConstraint; + +/** + The input toolbar's action bar + */ +@property (nonatomic, weak, readonly) RoomActionsBar *actionsBar; + +/** + The attach media button + */ +@property (nonatomic, weak, readonly) UIButton *attachMediaButton; + +/** + Adds a voice message toolbar view to be displayed inside this input toolbar + */ +- (void)setVoiceMessageToolbarView:(UIView *)toolbarView; @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 06c5096327..bf1bda55cf 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -17,34 +17,40 @@ #import "RoomInputToolbarView.h" -#import "ThemeService.h" #import "Riot-Swift.h" - #import "GBDeviceInfo_iOS.h" -#import "UINavigationController+Riot.h" +static const CGFloat kContextBarHeight = 24; +static const CGFloat kActionMenuAttachButtonSpringVelocity = 7; +static const CGFloat kActionMenuAttachButtonSpringDamping = .45; -#import "WidgetManager.h" -#import "IntegrationManagerViewController.h" +static const NSTimeInterval kSendModeAnimationDuration = .15; +static const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; +static const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; +static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; -@import GrowingTextView; +@interface RoomInputToolbarView() -const double kContextBarHeight = 24; -const NSTimeInterval kSendModeAnimationDuration = .15; -const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; -const CGFloat kActionMenuAttachButtonSpringVelocity = 7; -const CGFloat kActionMenuAttachButtonSpringDamping = .45; -const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; -const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; -const CGFloat kComposerContainerTrailingPadding = 12; +@property (nonatomic, weak) IBOutlet UIView *mainToolbarView; -@interface RoomInputToolbarView() -{ - // The intermediate action sheet - UIAlertController *actionSheet; -} +@property (nonatomic, weak) IBOutlet UIButton *attachMediaButton; @property (nonatomic, weak) IBOutlet RoomInputToolbarTextView *textView; +@property (nonatomic, weak) IBOutlet UIImageView *inputTextBackgroundView; + +@property (nonatomic, weak) IBOutlet UIImageView *inputContextImageView; +@property (nonatomic, weak) IBOutlet UILabel *inputContextLabel; +@property (nonatomic, weak) IBOutlet UIButton *inputContextButton; + +@property (nonatomic, weak) IBOutlet RoomActionsBar *actionsBar; + +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *inputContextViewHeightConstraint; + +@property (nonatomic, weak) UIView *voiceMessageToolbarView; + @property (nonatomic, assign) CGFloat expandedMainToolbarHeight; @end @@ -52,22 +58,10 @@ @interface RoomInputToolbarView() Date: Wed, 27 Oct 2021 10:15:57 +0300 Subject: [PATCH 2/2] vector-im/element-ios/issues/4976 - Fixed placeholder not displaying after being hidden for resize animations. --- .../InputToolbar/RoomInputToolbarTextView.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift index 4e8916e541..8a10976476 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarTextView.swift @@ -26,8 +26,17 @@ class RoomInputToolbarTextView: UITextView { weak var toolbarDelegate: RoomInputToolbarTextViewDelegate? - var placeholder: String? - var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) + var placeholder: String? { + didSet { + setNeedsDisplay() + } + } + + var placeholderColor: UIColor = UIColor(white: 0.8, alpha: 1.0) { + didSet { + setNeedsDisplay() + } + } var minHeight: CGFloat = 30.0 { didSet {