From 6f8dc60d5b7f558f9d2333eba71645a71cf49985 Mon Sep 17 00:00:00 2001 From: muukii Date: Thu, 16 Mar 2017 21:30:48 +0900 Subject: [PATCH 1/3] Deprecated delegate --- Demo/ViewController.swift | 6 +- NextGrowingTextView.xcodeproj/project.pbxproj | 4 +- .../NextGrowingInternalTextView.swift | 140 ++--- NextGrowingTextView/NextGrowingTextView.swift | 508 ++++++------------ 4 files changed, 230 insertions(+), 428 deletions(-) diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift index 7ddba9d..591257f 100644 --- a/Demo/ViewController.swift +++ b/Demo/ViewController.swift @@ -39,9 +39,9 @@ class ViewController: UIViewController { self.growingTextView.layer.cornerRadius = 4 self.growingTextView.backgroundColor = UIColor(white: 0.9, alpha: 1) - self.growingTextView.textContainerInset = UIEdgeInsets(top: 16, left: 0, bottom: 4, right: 0) + self.growingTextView.textView.textContainerInset = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0) self.growingTextView.placeholderAttributedText = NSAttributedString(string: "Placeholder text", - attributes: [NSFontAttributeName: self.growingTextView.font!, + attributes: [NSFontAttributeName: self.growingTextView.textView.font!, NSForegroundColorAttributeName: UIColor.gray ] ) @@ -58,7 +58,7 @@ class ViewController: UIViewController { @IBAction func handleSendButton(_ sender: AnyObject) { - self.growingTextView.text = "" + self.growingTextView.textView.text = "" self.view.endEditing(true) } diff --git a/NextGrowingTextView.xcodeproj/project.pbxproj b/NextGrowingTextView.xcodeproj/project.pbxproj index c39553b..0d7232c 100644 --- a/NextGrowingTextView.xcodeproj/project.pbxproj +++ b/NextGrowingTextView.xcodeproj/project.pbxproj @@ -48,7 +48,7 @@ 4BDB87E81DC0423F00E70D5B /* NextGrowingTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NextGrowingTextView.h; sourceTree = ""; }; 4BDB87E91DC0423F00E70D5B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4BDB87F01DC0425600E70D5B /* NextGrowingInternalTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NextGrowingInternalTextView.swift; sourceTree = ""; }; - 4BDB87F11DC0425600E70D5B /* NextGrowingTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NextGrowingTextView.swift; sourceTree = ""; }; + 4BDB87F11DC0425600E70D5B /* NextGrowingTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = NextGrowingTextView.swift; sourceTree = ""; tabWidth = 2; }; 4BDB87F81DC0426A00E70D5B /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4BDB87FA1DC0426A00E70D5B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4BDB87FC1DC0426A00E70D5B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -462,6 +462,7 @@ 4BDB87EF1DC0423F00E70D5B /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 4BDB88071DC0426A00E70D5B /* Build configuration list for PBXNativeTarget "Demo" */ = { isa = XCConfigurationList; @@ -470,6 +471,7 @@ 4BDB88091DC0426A00E70D5B /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/NextGrowingTextView/NextGrowingInternalTextView.swift b/NextGrowingTextView/NextGrowingInternalTextView.swift index a685c74..0314ae7 100644 --- a/NextGrowingTextView/NextGrowingInternalTextView.swift +++ b/NextGrowingTextView/NextGrowingInternalTextView.swift @@ -26,76 +26,80 @@ import UIKit // MARK: - NextGrowingInternalTextView: UITextView internal class NextGrowingInternalTextView: UITextView { - - // MARK: - Internal - - override init(frame: CGRect, textContainer: NSTextContainer?) { - super.init(frame: frame, textContainer: textContainer) - - NotificationCenter.default.addObserver(self, selector: #selector(NextGrowingInternalTextView.textDidChangeNotification(_ :)), name: NSNotification.Name.UITextViewTextDidChange, object: self) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - override var text: String! { - didSet { - self.updatePlaceholder() - } - } - - var placeholderAttributedText: NSAttributedString? { - didSet { - self.setNeedsDisplay() - } - } - - override func layoutSubviews() { - super.layoutSubviews() - self.setNeedsDisplay() - } - - override func draw(_ rect: CGRect) { - - super.draw(rect) - - guard self.displayPlaceholder == true else { - return - } - - let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = self.textAlignment - - let targetRect = CGRect(x: 5 + self.textContainerInset.left, - y: self.textContainerInset.top, - width: self.frame.size.width - (self.textContainerInset.left + self.textContainerInset.right), - height: self.frame.size.height - (self.textContainerInset.top + self.textContainerInset.bottom)) - - let attributedString = self.placeholderAttributedText - attributedString?.draw(in: targetRect) + + // MARK: - Internal + + var didChange: () -> Void = {} + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + + NotificationCenter.default.addObserver(self, selector: #selector(NextGrowingInternalTextView.textDidChangeNotification(_ :)), name: NSNotification.Name.UITextViewTextDidChange, object: self) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override var text: String! { + didSet { + didChange() + updatePlaceholder() } - - // MARK: Private - - fileprivate var displayPlaceholder: Bool = true { - didSet { - if oldValue != self.displayPlaceholder { - self.setNeedsDisplay() - } - } + } + + var placeholderAttributedText: NSAttributedString? { + didSet { + setNeedsDisplay() } - - fileprivate dynamic func textDidChangeNotification(_ notification: Notification) { - - self.updatePlaceholder() + } + + override func layoutSubviews() { + super.layoutSubviews() + setNeedsDisplay() + } + + override func draw(_ rect: CGRect) { + + super.draw(rect) + + guard displayPlaceholder == true else { + return } - - fileprivate func updatePlaceholder() { - self.displayPlaceholder = self.text.characters.count == 0 + + let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = self.textAlignment + + let targetRect = CGRect(x: 5 + textContainerInset.left, + y: textContainerInset.top, + width: frame.size.width - (textContainerInset.left + textContainerInset.right), + height: frame.size.height - (textContainerInset.top + textContainerInset.bottom)) + + let attributedString = placeholderAttributedText + attributedString?.draw(in: targetRect) + } + + // MARK: Private + + private var displayPlaceholder: Bool = true { + didSet { + if oldValue != displayPlaceholder { + setNeedsDisplay() + } } + } + + private dynamic func textDidChangeNotification(_ notification: Notification) { + + updatePlaceholder() + didChange() + } + + private func updatePlaceholder() { + displayPlaceholder = text.characters.count == 0 + } } diff --git a/NextGrowingTextView/NextGrowingTextView.swift b/NextGrowingTextView/NextGrowingTextView.swift index b53a99c..d8f7ca4 100644 --- a/NextGrowingTextView/NextGrowingTextView.swift +++ b/NextGrowingTextView/NextGrowingTextView.swift @@ -28,429 +28,225 @@ import UIKit open class NextGrowingTextView: UIScrollView { + // MARK: - Public - // MARK: - Public - - open class Delegates { - open var shouldChangeTextInRange: (_ range: NSRange, _ replacementText: String) -> Bool = { _ in true } - open var shouldInteractWithURL: (_ URL: URL, _ inRange: NSRange) -> Bool = { _ in true } - open var shouldInteractWithTextAttachment: (_ textAttachment: NSTextAttachment, _ inRange: NSRange) -> Bool = { _ in true } - open var textViewDidBeginEditing: (NextGrowingTextView) -> Void = { _ in } - open var textViewDidChangeSelection: (NextGrowingTextView) -> Void = { _ in } - open var textViewDidEndEditing: (NextGrowingTextView) -> Void = { _ in } - open var textViewShouldBeginEditing: (NextGrowingTextView) -> Bool = { _ in true } - open var textViewShouldEndEditing: (NextGrowingTextView) -> Bool = { _ in true } - open var textViewDidChange: (NextGrowingTextView) -> Void = { _ in } - - open var willChangeHeight: (CGFloat) -> Void = { _ in } - open var didChangeHeight: (CGFloat) -> Void = { _ in } - } - - open var delegates = Delegates() - - open var minNumberOfLines: Int { - get { - return _minNumberOfLines - } - set { - guard newValue > 1 else { - minHeight = 1 - return - } - - minHeight = simulateHeight(newValue) - _minNumberOfLines = newValue - } - } + open class Delegates { + open var willChangeHeight: (CGFloat) -> Void = { _ in } + open var didChangeHeight: (CGFloat) -> Void = { _ in } + } - open var maxNumberOfLines: Int { - get { - return _maxNumberOfLines - } - set { + open var delegates = Delegates() - guard newValue > 1 else { - maxHeight = 1 - return - } + open var textView: UITextView { + return _textView + } - maxHeight = simulateHeight(newValue) - _maxNumberOfLines = newValue - } + open var minNumberOfLines: Int { + get { + return _minNumberOfLines } + set { + guard newValue > 1 else { + minHeight = 1 + return + } - open var disableAutomaticScrollToBottom = false - - public override init(frame: CGRect) { - textView = NextGrowingInternalTextView(frame: CGRect(origin: CGPoint.zero, size: frame.size)) - previousFrame = frame - - super.init(frame: frame) - - setup() - } - - public required init?(coder aDecoder: NSCoder) { - - textView = NextGrowingInternalTextView(frame: CGRect.zero) - - super.init(coder: aDecoder) - - textView.frame = bounds - previousFrame = frame - setup() + minHeight = simulateHeight(newValue) + _minNumberOfLines = newValue } + } - open override func layoutSubviews() { - super.layoutSubviews() - if previousFrame.width != bounds.width { - previousFrame = frame - fitToScrollView() - } + open var maxNumberOfLines: Int { + get { + return _maxNumberOfLines } + set { - // MARK: UIResponder + guard newValue > 1 else { + maxHeight = 1 + return + } - open override var inputView: UIView? { - get { - return textView.inputView - } - set { - textView.inputView = newValue - } + maxHeight = simulateHeight(newValue) + _maxNumberOfLines = newValue } + } - open override var isFirstResponder: Bool { - return self.textView.isFirstResponder - } + open var disableAutomaticScrollToBottom = false - open override func becomeFirstResponder() -> Bool { - return self.textView.becomeFirstResponder() - } + public override init(frame: CGRect) { + _textView = NextGrowingInternalTextView(frame: CGRect(origin: CGPoint.zero, size: frame.size)) + previousFrame = frame - open override func resignFirstResponder() -> Bool { - return self.textView.resignFirstResponder() - } - - open override var intrinsicContentSize: CGSize { - return self.measureFrame(self.measureTextViewSize()).size - } - - open override func reloadInputViews() { - super.reloadInputViews() - textView.reloadInputViews() - } + super.init(frame: frame) - // MARK: Private + setup() + } - fileprivate let textView: NextGrowingInternalTextView + public required init?(coder aDecoder: NSCoder) { - fileprivate var _maxNumberOfLines: Int = 0 - fileprivate var _minNumberOfLines: Int = 0 - fileprivate var maxHeight: CGFloat = 0 - fileprivate var minHeight: CGFloat = 0 + _textView = NextGrowingInternalTextView(frame: CGRect.zero) - fileprivate func setup() { + super.init(coder: aDecoder) - self.textView.delegate = self - self.textView.isScrollEnabled = false - self.textView.font = UIFont.systemFont(ofSize: 16) - self.textView.backgroundColor = UIColor.clear - self.addSubview(textView) - self.minHeight = simulateHeight(1) - self.maxNumberOfLines = 3 - } + _textView.frame = bounds + previousFrame = frame + setup() + } - fileprivate func measureTextViewSize() -> CGSize { - return textView.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.infinity)) + open override func layoutSubviews() { + super.layoutSubviews() + if previousFrame.width != bounds.width { + previousFrame = frame + fitToScrollView() } + } - fileprivate func measureFrame(_ contentSize: CGSize) -> CGRect { - - let selfSize: CGSize - - if contentSize.height < self.minHeight || !self.textView.hasText { - selfSize = CGSize(width: contentSize.width, height: self.minHeight) - } else if self.maxHeight > 0 && contentSize.height > self.maxHeight { - selfSize = CGSize(width: contentSize.width, height: self.maxHeight) - } else { - selfSize = contentSize - } + // MARK: UIResponder - var _frame = frame - _frame.size.height = selfSize.height - return _frame + open override var inputView: UIView? { + get { + return _textView.inputView } - - fileprivate func fitToScrollView() { - - let shouldScrollToBottom = contentOffset.y == contentSize.height - frame.height - let actualTextViewSize = measureTextViewSize() - let oldScrollViewFrame = frame - - var _frame = bounds - _frame.origin = CGPoint.zero - _frame.size.height = actualTextViewSize.height - textView.frame = _frame - contentSize = _frame.size - - let newScrollViewFrame = measureFrame(actualTextViewSize) - - if oldScrollViewFrame.height != newScrollViewFrame.height && newScrollViewFrame.height <= maxHeight { - flashScrollIndicators() - delegates.willChangeHeight(newScrollViewFrame.height) - } - - frame = newScrollViewFrame - - if shouldScrollToBottom { - scrollToBottom() - } - - invalidateIntrinsicContentSize() - delegates.didChangeHeight(frame.height) + set { + _textView.inputView = newValue } + } - fileprivate func scrollToBottom() { - if !disableAutomaticScrollToBottom { - let offset = contentOffset - contentOffset = CGPoint(x: offset.x, y: contentSize.height - frame.height) - } - } - - fileprivate func updateMinimumAndMaximumHeight() { - self.minHeight = simulateHeight(1) - self.maxHeight = simulateHeight(self.maxNumberOfLines) - self.fitToScrollView() - } - - fileprivate func simulateHeight(_ line: Int) -> CGFloat { - - let saveText = textView.text - var newText = "-" - - self.textView.delegate = nil - self.textView.isHidden = true + open override var isFirstResponder: Bool { + return self._textView.isFirstResponder + } - for _ in 0.. Bool { + return self._textView.becomeFirstResponder() + } - textView.text = newText + open override func resignFirstResponder() -> Bool { + return self._textView.resignFirstResponder() + } - let height = measureTextViewSize().height + open override var intrinsicContentSize: CGSize { + return self.measureFrame(self.measureTextViewSize()).size + } - self.textView.text = saveText - self.textView.isHidden = false - self.textView.delegate = self + open override func reloadInputViews() { + super.reloadInputViews() + _textView.reloadInputViews() + } - return height - } - - fileprivate var previousFrame: CGRect = CGRect.zero -} - - -// MARK: - TextView Properties + // MARK: Private -extension NextGrowingTextView { + fileprivate let _textView: NextGrowingInternalTextView - // MARK: TextView Extension + fileprivate var _maxNumberOfLines: Int = 0 + fileprivate var _minNumberOfLines: Int = 0 + fileprivate var maxHeight: CGFloat = 0 + fileprivate var minHeight: CGFloat = 0 - public var placeholderAttributedText: NSAttributedString? { - get {return textView.placeholderAttributedText } - set {textView.placeholderAttributedText = newValue } - } + fileprivate func setup() { - // MARK: TextView + self._textView.isScrollEnabled = false + self._textView.font = UIFont.systemFont(ofSize: 16) + self._textView.backgroundColor = UIColor.clear + self.addSubview(_textView) + self.minHeight = simulateHeight(1) + self.maxNumberOfLines = 3 - public var returnKeyType: UIReturnKeyType { - get { return textView.returnKeyType } - set { textView.returnKeyType = newValue } - } - - public var spellCheckingType: UITextSpellCheckingType { - get { return textView.spellCheckingType } - set { textView.spellCheckingType = newValue } - } - - public var autocorrectionType: UITextAutocorrectionType { - get { return textView.autocorrectionType } - set { textView.autocorrectionType = newValue } - } - - public var autocapitalizationType: UITextAutocapitalizationType { - get { return textView.autocapitalizationType } - set { textView.autocapitalizationType = newValue } + _textView.didChange = { [weak self] in + self?.fitToScrollView() } + } - public var text: String! { - get { return textView.text } - set { - textView.text = newValue - fitToScrollView() - } - } + fileprivate func measureTextViewSize() -> CGSize { + return _textView.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.infinity)) + } - public var font: UIFont? { - get { return textView.font } - set { - textView.font = newValue - updateMinimumAndMaximumHeight() - } - } + fileprivate func measureFrame(_ contentSize: CGSize) -> CGRect { - public var textColor: UIColor? { - get { return textView.textColor } - set { textView.textColor = newValue } - } + let selfSize: CGSize - public var textAlignment: NSTextAlignment { - get { return textView.textAlignment } - set { textView.textAlignment = newValue } + if contentSize.height < self.minHeight || !self._textView.hasText { + selfSize = CGSize(width: contentSize.width, height: self.minHeight) + } else if self.maxHeight > 0 && contentSize.height > self.maxHeight { + selfSize = CGSize(width: contentSize.width, height: self.maxHeight) + } else { + selfSize = contentSize } - public var selectedRange: NSRange { - get { return textView.selectedRange } - set { textView.selectedRange = newValue } - } + var _frame = frame + _frame.size.height = selfSize.height + return _frame + } - public var dataDetectorTypes: UIDataDetectorTypes { - get { return textView.dataDetectorTypes } - set { textView.dataDetectorTypes = newValue } - } + fileprivate func fitToScrollView() { - public var selectable: Bool { - get { return self.textView.isSelectable } - set { self.textView.isSelectable = newValue } - } + let shouldScrollToBottom = contentOffset.y == contentSize.height - frame.height + let actualTextViewSize = measureTextViewSize() + let oldScrollViewFrame = frame - public var allowsEditingTextAttributes: Bool { - get { return textView.allowsEditingTextAttributes } - set { textView.allowsEditingTextAttributes = newValue } - } + var _frame = bounds + _frame.origin = CGPoint.zero + _frame.size.height = actualTextViewSize.height + _textView.frame = _frame + contentSize = _frame.size - public var attributedText: NSAttributedString! { - get { return textView.attributedText } - set { - textView.attributedText = newValue - fitToScrollView() - } - } + let newScrollViewFrame = measureFrame(actualTextViewSize) - public var typingAttributes: [String : Any] { - get { return self.textView.typingAttributes } - set { self.textView.typingAttributes = newValue } + if oldScrollViewFrame.height != newScrollViewFrame.height && newScrollViewFrame.height <= maxHeight { + flashScrollIndicators() + delegates.willChangeHeight(newScrollViewFrame.height) } - public func scrollRangeToVisible(_ range: NSRange) { - self.textView.scrollRangeToVisible(range) - } + frame = newScrollViewFrame - public var textViewInputView: UIView? { - get { return textView.inputView } - set { textView.inputView = newValue } - } - - public var keyboardType: UIKeyboardType { - get { return textView.keyboardType } - set { textView.keyboardType = newValue } + if shouldScrollToBottom { + scrollToBottom() } - public var textViewInputAccessoryView: UIView? { - get { return textView.inputAccessoryView } - set { textView.inputAccessoryView = newValue } - } + invalidateIntrinsicContentSize() + delegates.didChangeHeight(frame.height) + } - public var clearsOnInsertion: Bool { - get { return textView.clearsOnInsertion } - set { textView.clearsOnInsertion = newValue } + fileprivate func scrollToBottom() { + if !disableAutomaticScrollToBottom { + let offset = contentOffset + contentOffset = CGPoint(x: offset.x, y: contentSize.height - frame.height) } + } - public var textContainer: NSTextContainer { - return textView.textContainer - } + fileprivate func updateMinimumAndMaximumHeight() { + self.minHeight = simulateHeight(1) + self.maxHeight = simulateHeight(self.maxNumberOfLines) + self.fitToScrollView() + } - public var textContainerInset: UIEdgeInsets { - get { return textView.textContainerInset } - set { - textView.textContainerInset = newValue - updateMinimumAndMaximumHeight() - } - } + fileprivate func simulateHeight(_ line: Int) -> CGFloat { - public var layoutManger: NSLayoutManager { - return textView.layoutManager - } + let saveText = _textView.text + var newText = "-" - public var textStorage: NSTextStorage { - return textView.textStorage - } + self._textView.isHidden = true - public var linkTextAttributes: [String : Any]! { - get { return self.textView.linkTextAttributes} - set { self.textView.linkTextAttributes = newValue } + for _ in 0.. Bool { - return self.delegates.shouldChangeTextInRange(range, text) - } - - public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { - return self.delegates.shouldInteractWithURL(URL, characterRange) - } - - public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool { - return self.delegates.shouldInteractWithTextAttachment(textAttachment, characterRange) - } - - public func textViewDidBeginEditing(_ textView: UITextView) { - self.delegates.textViewDidBeginEditing(self) - } - - public func textViewDidChangeSelection(_ textView: UITextView) { - self.delegates.textViewDidChangeSelection(self) - } - - public func textViewDidEndEditing(_ textView: UITextView) { - self.delegates.textViewDidEndEditing(self) - } - - public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { - return self.delegates.textViewShouldBeginEditing(self) - } - - public func textViewShouldEndEditing(_ textView: UITextView) -> Bool { - return self.delegates.textViewShouldEndEditing(self) - } - - public func textViewDidChange(_ textView: UITextView) { - - delegates.textViewDidChange(self) - - fitToScrollView() - } +extension NextGrowingTextView { + public var placeholderAttributedText: NSAttributedString? { + get { return _textView.placeholderAttributedText } + set { _textView.placeholderAttributedText = newValue } + } } From 82604ae0f6e850c70dbb02c0aaf1869f056f9708 Mon Sep 17 00:00:00 2001 From: muukii Date: Mon, 1 May 2017 21:37:08 +0900 Subject: [PATCH 2/3] Revise APIs --- .../NextGrowingInternalTextView.swift | 16 +- NextGrowingTextView/NextGrowingTextView.swift | 160 +++++++++--------- 2 files changed, 89 insertions(+), 87 deletions(-) diff --git a/NextGrowingTextView/NextGrowingInternalTextView.swift b/NextGrowingTextView/NextGrowingInternalTextView.swift index 0314ae7..769bffe 100644 --- a/NextGrowingTextView/NextGrowingInternalTextView.swift +++ b/NextGrowingTextView/NextGrowingInternalTextView.swift @@ -72,13 +72,15 @@ internal class NextGrowingInternalTextView: UITextView { } let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = self.textAlignment - - let targetRect = CGRect(x: 5 + textContainerInset.left, - y: textContainerInset.top, - width: frame.size.width - (textContainerInset.left + textContainerInset.right), - height: frame.size.height - (textContainerInset.top + textContainerInset.bottom)) - + paragraphStyle.alignment = textAlignment + + let targetRect = CGRect( + x: 5 + textContainerInset.left, + y: textContainerInset.top, + width: frame.size.width - (textContainerInset.left + textContainerInset.right), + height: frame.size.height - (textContainerInset.top + textContainerInset.bottom) + ) + let attributedString = placeholderAttributedText attributedString?.draw(in: targetRect) } diff --git a/NextGrowingTextView/NextGrowingTextView.swift b/NextGrowingTextView/NextGrowingTextView.swift index d8f7ca4..76cff79 100644 --- a/NextGrowingTextView/NextGrowingTextView.swift +++ b/NextGrowingTextView/NextGrowingTextView.swift @@ -23,18 +23,19 @@ import Foundation import UIKit - // MARK: - NextGrowingTextView: UIScrollView open class NextGrowingTextView: UIScrollView { - - // MARK: - Public - + + // MARK: - Nested types + open class Delegates { open var willChangeHeight: (CGFloat) -> Void = { _ in } open var didChangeHeight: (CGFloat) -> Void = { _ in } } + // MARK: - Properties + open var delegates = Delegates() open var textView: UITextView { @@ -47,11 +48,11 @@ open class NextGrowingTextView: UIScrollView { } set { guard newValue > 1 else { - minHeight = 1 + _minHeight = 1 return } - minHeight = simulateHeight(newValue) + _minHeight = simulateHeight(newValue) _minNumberOfLines = newValue } } @@ -63,20 +64,60 @@ open class NextGrowingTextView: UIScrollView { set { guard newValue > 1 else { - maxHeight = 1 + _maxHeight = 1 return } - maxHeight = simulateHeight(newValue) + _maxHeight = simulateHeight(newValue) _maxNumberOfLines = newValue } } open var disableAutomaticScrollToBottom = false + + open var placeholderAttributedText: NSAttributedString? { + get { return _textView.placeholderAttributedText } + set { _textView.placeholderAttributedText = newValue } + } + + open override var inputView: UIView? { + get { + return _textView.inputView + } + set { + _textView.inputView = newValue + } + } + + open override var isFirstResponder: Bool { + return _textView.isFirstResponder + } + + open override func becomeFirstResponder() -> Bool { + return _textView.becomeFirstResponder() + } + + open override func resignFirstResponder() -> Bool { + return _textView.resignFirstResponder() + } + + open override var intrinsicContentSize: CGSize { + return measureFrame(measureTextViewSize()).size + } + + private let _textView: NextGrowingInternalTextView + private var _maxNumberOfLines: Int = 0 + private var _minNumberOfLines: Int = 0 + private var _maxHeight: CGFloat = 0 + private var _minHeight: CGFloat = 0 + private var _previousFrame: CGRect = CGRect.zero + + // MARK: - Initializers public override init(frame: CGRect) { + _textView = NextGrowingInternalTextView(frame: CGRect(origin: CGPoint.zero, size: frame.size)) - previousFrame = frame + _previousFrame = frame super.init(frame: frame) @@ -90,85 +131,53 @@ open class NextGrowingTextView: UIScrollView { super.init(coder: aDecoder) _textView.frame = bounds - previousFrame = frame + _previousFrame = frame setup() } + + // MARK: - Functions open override func layoutSubviews() { super.layoutSubviews() - if previousFrame.width != bounds.width { - previousFrame = frame + if _previousFrame.width != bounds.width { + _previousFrame = frame fitToScrollView() } } // MARK: UIResponder - - open override var inputView: UIView? { - get { - return _textView.inputView - } - set { - _textView.inputView = newValue - } - } - - open override var isFirstResponder: Bool { - return self._textView.isFirstResponder - } - - open override func becomeFirstResponder() -> Bool { - return self._textView.becomeFirstResponder() - } - - open override func resignFirstResponder() -> Bool { - return self._textView.resignFirstResponder() - } - - open override var intrinsicContentSize: CGSize { - return self.measureFrame(self.measureTextViewSize()).size - } - + open override func reloadInputViews() { super.reloadInputViews() _textView.reloadInputViews() } - // MARK: Private - - fileprivate let _textView: NextGrowingInternalTextView - - fileprivate var _maxNumberOfLines: Int = 0 - fileprivate var _minNumberOfLines: Int = 0 - fileprivate var maxHeight: CGFloat = 0 - fileprivate var minHeight: CGFloat = 0 - - fileprivate func setup() { + private func setup() { - self._textView.isScrollEnabled = false - self._textView.font = UIFont.systemFont(ofSize: 16) - self._textView.backgroundColor = UIColor.clear - self.addSubview(_textView) - self.minHeight = simulateHeight(1) - self.maxNumberOfLines = 3 + _textView.isScrollEnabled = false + _textView.font = UIFont.systemFont(ofSize: 16) + _textView.backgroundColor = UIColor.clear + addSubview(_textView) + _minHeight = simulateHeight(1) + maxNumberOfLines = 3 _textView.didChange = { [weak self] in self?.fitToScrollView() } } - fileprivate func measureTextViewSize() -> CGSize { + private func measureTextViewSize() -> CGSize { return _textView.sizeThatFits(CGSize(width: self.bounds.width, height: CGFloat.infinity)) } - fileprivate func measureFrame(_ contentSize: CGSize) -> CGRect { + private func measureFrame(_ contentSize: CGSize) -> CGRect { let selfSize: CGSize - if contentSize.height < self.minHeight || !self._textView.hasText { - selfSize = CGSize(width: contentSize.width, height: self.minHeight) - } else if self.maxHeight > 0 && contentSize.height > self.maxHeight { - selfSize = CGSize(width: contentSize.width, height: self.maxHeight) + if contentSize.height < _minHeight || !_textView.hasText { + selfSize = CGSize(width: contentSize.width, height: _minHeight) + } else if _maxHeight > 0 && contentSize.height > _maxHeight { + selfSize = CGSize(width: contentSize.width, height: _maxHeight) } else { selfSize = contentSize } @@ -178,7 +187,7 @@ open class NextGrowingTextView: UIScrollView { return _frame } - fileprivate func fitToScrollView() { + private func fitToScrollView() { let shouldScrollToBottom = contentOffset.y == contentSize.height - frame.height let actualTextViewSize = measureTextViewSize() @@ -192,7 +201,7 @@ open class NextGrowingTextView: UIScrollView { let newScrollViewFrame = measureFrame(actualTextViewSize) - if oldScrollViewFrame.height != newScrollViewFrame.height && newScrollViewFrame.height <= maxHeight { + if oldScrollViewFrame.height != newScrollViewFrame.height && newScrollViewFrame.height <= _maxHeight { flashScrollIndicators() delegates.willChangeHeight(newScrollViewFrame.height) } @@ -207,25 +216,25 @@ open class NextGrowingTextView: UIScrollView { delegates.didChangeHeight(frame.height) } - fileprivate func scrollToBottom() { + private func scrollToBottom() { if !disableAutomaticScrollToBottom { let offset = contentOffset contentOffset = CGPoint(x: offset.x, y: contentSize.height - frame.height) } } - fileprivate func updateMinimumAndMaximumHeight() { - self.minHeight = simulateHeight(1) - self.maxHeight = simulateHeight(self.maxNumberOfLines) - self.fitToScrollView() + private func updateMinimumAndMaximumHeight() { + _minHeight = simulateHeight(1) + _maxHeight = simulateHeight(maxNumberOfLines) + fitToScrollView() } - fileprivate func simulateHeight(_ line: Int) -> CGFloat { + private func simulateHeight(_ line: Int) -> CGFloat { let saveText = _textView.text var newText = "-" - self._textView.isHidden = true + _textView.isHidden = true for _ in 0.. Date: Mon, 1 May 2017 21:39:44 +0900 Subject: [PATCH 3/3] Update Podspec --- NextGrowingTextView.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NextGrowingTextView.podspec b/NextGrowingTextView.podspec index c8aaf93..4a8fc6f 100644 --- a/NextGrowingTextView.podspec +++ b/NextGrowingTextView.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "NextGrowingTextView" - s.version = "0.8.5" - s.summary = "The next in the generations of 'growing textviews' optimized for iOS 7 and above." + s.version = "1.0.0" + s.summary = "The next in the generations of 'growing textviews' optimized for iOS 8 and above." s.homepage = "https://github.com/muukii/NextGrowingTextView" s.license = 'MIT' s.author = { "muukii" => "m@muukii.me" }