Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions AnimatedTextInput/Classes/AnimatedTextInputFieldConfigurator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public struct AnimatedTextInputFieldConfigurator {
case phone
case selection
case customSelection(isRightViewEnabled: Bool, rightViewImage: UIImage?)
case multiline
case multiline()
case multilineRestricted(maxHeight: CGFloat)
case generic(textInput: TextInput)
}

Expand All @@ -28,8 +29,10 @@ public struct AnimatedTextInputFieldConfigurator {
return AnimatedTextInputPhoneConfigurator.generate()
case .selection:
return AnimatedTextInputSelectionConfigurator.generate()
case .multiline:
return AnimatedTextInputMultilineConfigurator.generate()
case .multiline():
return AnimatedTextInputMultilineConfigurator.generate(using: nil)
case .multilineRestricted(let maxHeight):
return AnimatedTextInputMultilineConfigurator.generate(using: maxHeight)
case .generic(let textInput):
return textInput
case .customSelection(let isRightViewEnabled, let rightViewImage):
Expand Down Expand Up @@ -138,12 +141,24 @@ fileprivate struct AnimatedTextInputCustomSelectionConfigurator {

fileprivate struct AnimatedTextInputMultilineConfigurator {

static func generate() -> TextInput {
static func generate(using maxHeight: CGFloat?) -> TextInput {
let textView = AnimatedTextView()
textView.textContainerInset = .zero
textView.backgroundColor = .clear
textView.isScrollEnabled = false
textView.autocorrectionType = .no

// Use the maximum allowed height if one is provided by the caller.
if let providedMaxHeight = maxHeight {

// Pass the value along to the TextView.
textView.maximumHeightOfMultilineLabel = providedMaxHeight

// Add a constraint to set the maximum height that must not be exeeded.
let constraint = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .lessThanOrEqual, toItem: nil, attribute: .height, multiplier: 1, constant: providedMaxHeight)
constraint.identifier = "MaxHeightOfTextView"
textView.view.addConstraint(constraint)
}
return textView
}
}
44 changes: 43 additions & 1 deletion AnimatedTextInput/Classes/AnimatedTextView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UIKit

final public class AnimatedTextView: UITextView {


public var textAttributes: [NSAttributedString.Key: Any]? {
didSet {
Expand All @@ -19,6 +20,13 @@ final public class AnimatedTextView: UITextView {

public weak var textInputDelegate: TextInputDelegate?

/// The maximum allowed height of a multiline label. When set, the TextView will not grow above this height.
var maximumHeightOfMultilineLabel: CGFloat?

/// Constraint that limits the maximum height of a multiline textview.
fileprivate var heightConstraintForMultilineLabel: NSLayoutConstraint?


override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)

Expand Down Expand Up @@ -96,7 +104,41 @@ extension AnimatedTextView: UITextViewDelegate {
let range = textView.selectedRange
textView.attributedText = NSAttributedString(string: textView.text, attributes: textAttributes)
textView.selectedRange = range


// Force-refresh the layout as we need the contentSize based on the current amount of text.
textView.setNeedsLayout()
textView.layoutIfNeeded()

// Perform a check to see if the TextView should have a maximum height. If so, check if the desired contentHeight is above the max height.
if let maxHeight = maximumHeightOfMultilineLabel, textView.contentSize.height >= maxHeight {

// Check if the height constraint is not added, yet. Create and add it if so.
if heightConstraintForMultilineLabel == nil {

// Enable scrolling:
textView.isScrollEnabled = true

// Set new maximum height as current height constraint. Since the maximum allowed height is already reached, this constraint defines the current height of the text input to be the maximum height:
heightConstraintForMultilineLabel = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: maxHeight)
heightConstraintForMultilineLabel!.identifier = "CurrentHeightOfTextView"
textView.addConstraint(heightConstraintForMultilineLabel!)

// Scroll to the bottom as the user may enter even more text and want to see where the text ist put.
let bottomRect = CGRect(x: 0, y: textView.contentSize.height-1, width: textView.contentSize.width, height: 1)
textView.scrollRectToVisible(bottomRect, animated: true)
}
}
else {
// Remove the constraint and set it to nil in order to avoid re-adding it later.
if let existingConstraint = heightConstraintForMultilineLabel {
textView.removeConstraint(existingConstraint)
heightConstraintForMultilineLabel = nil

// Disable scrolling:
textView.isScrollEnabled = false
}
}

textInputDelegate?.textInputDidChange(textInput: self)
}

Expand Down
2 changes: 1 addition & 1 deletion Example/AnimatedTextInput/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ViewController: UIViewController {
} as (() -> Void)

textInputs[4].placeHolderText = "Multiline"
textInputs[4].type = .multiline
textInputs[4].type = .multilineRestricted(maxHeight: 100)
textInputs[4].showCharacterCounterLabel(with: 160)
textInputs[4].keyboardAppearance = .dark

Expand Down
2 changes: 1 addition & 1 deletion Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ DEPENDENCIES:
- KIF (~> 3.5)

SPEC REPOS:
https://github.com/cocoapods/specs:
https://github.com/cocoapods/specs.git:
- FBSnapshotTestCase
- KIF

Expand Down
12 changes: 6 additions & 6 deletions Tests/SnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,22 @@ class AnimatedTextInputSnapshotTests: FBSnapshotTestCase {
//Multiline type

func testNormalStateMultiline() {
sut.type = .multiline
sut.type = .multiline()
sut.placeHolderText = "Placeholder"

verifySUT()
}

func testEmptyActiveStateMultiline() {
sut.type = .multiline
sut.type = .multiline()
sut.placeHolderText = "Placeholder"
sut.becomeFirstResponder()

verifySUT()
}

func testFilledActiveStateMultiline() {
sut.type = .multiline
sut.type = .multiline()
sut.placeHolderText = "Placeholder"
sut.text = "A very long text to fill a few lines. A very long text to fill a few lines. A very long text to fill a few lines. A very long text to fill a few lines"
sut.becomeFirstResponder()
Expand All @@ -67,7 +67,7 @@ class AnimatedTextInputSnapshotTests: FBSnapshotTestCase {
}

func testFilledActiveStateMultilineWithCounter() {
sut.type = .multiline
sut.type = .multiline()
sut.placeHolderText = "Placeholder"
sut.text = "A very long text to fill a few lines. A very long text to fill a few lines. A very long text to fill a few lines. A very long text to fill a few lines"
sut.showCharacterCounterLabel()
Expand All @@ -77,7 +77,7 @@ class AnimatedTextInputSnapshotTests: FBSnapshotTestCase {
}

func testInactiveFilledStateMultiline() {
sut.type = .multiline
sut.type = .multiline()
sut.placeHolderText = "Placeholder"
sut.text = "Input text"

Expand Down Expand Up @@ -112,7 +112,7 @@ class AnimatedTextInputSnapshotTests: FBSnapshotTestCase {
}

func testAnimatedTextFieldActiveCustomStyle() {
sut.type = .multiline
sut.type = .multiline()
sut.style = CustomTextInputStyle()
sut.placeHolderText = "Placeholder"
sut.text = "Input text"
Expand Down