AutoHeightEditor is a custom TextEditor library with Dynamic Height functionality.
I created this custom TextEditor because it was a requirement for a project I was working on.
In the project, an input interface that dynamically adjusts in height was needed. From iOS 16, an input interface that works as Dynamic Height can be easily used through the axis parameter of TextField.
However, the minimum supported version of the project was decided to be iOS 15.0+, and for multiple lines of text input, TextEditor had to be used.
As those who have used the standard API TextEditor might agree, it lacks some features compared to TextField, especially it takes up the maximum possible height unless a specific height is designated.
To solve this, I created the AutoHeightEditor, which dynamically calculates the appropriate height.
You can check out the detailed background and implementation process on my blog.
The main feature of AutoHeightEditor is essentially Dynamic Height.
It changes the height of the TextEditor in real-time based on font height, line spacing, text length, and new line characters.
- Count the number of
\n(new line characters) in the text. - Calculate the width of the
TextEditorand the length of the entered text to determine how many times automatic line breaks should occur. - Combine the counts from steps 1 and 2 to calculate the total number of line breaks.
- Calculate the total height of the
TextEditorby considering the font size, line spacing, and number of line breaks.
The height changes from a minimum of 1 line to a maximum of maxLine based on user input. Options like maximum line numbers, used font, line spacing, and activation status are taken as parameters.
As I customized a component for personal use to fit into a library, I considered the environment of other users and added the following features:
- Accepts
isEnabledbinding for external control of activation status. - Option to choose the use of a fixed Border stroke.
- Customizable disabled placeholder text.
- Reflects the result of regular expression matching in a bound Bool variable.
As mentioned in the production background, iOS 16 is still a version that is burdensome to apply in practice. Therefore, it was implemented to be usable from iOS 14, where TextEditor first appeared.
public init (
text: Binding<String>,
font: Font = .body,
lineSpace: CGFloat = 2,
maxLine: Int,
hasBorder: Bool,
isEnabled: Binding<Bool>,
disabledPlaceholder: String,
regExpUse: RegExpUse
)text: Binding<String>The text string bound to the editor. It is used by injecting a binding from the outside.
font: FontThe font type applied to the text. body is injected as the Default Value, and if you have a different desired font, you can inject and use it.
lineSpace: CGFloatThe line spacing between text lines. 2 is injected as the Default Value, and if you have a different desired value, you can inject and use it.
maxLine: IntThe maximum number of lines for which the editor's height increases. As the input lines increase, the height of the editor grows up to maxLine, and then does not increase further.
hasBorder: BoolDetermines whether to use the default provided Stroke. The basic Stroke comes with a Gray color and a CornerRadius of 20.
isEnabled: Binding<Bool>Determines whether the editor is active. It is injected and used from the outside via binding.
disabledPlaceholder: StringA message to guide the user when the editor is disabled.
public enum RegExpUse {
case use(pattern: String, isMatched: Binding<Bool>)
case none
}
regExpUse: RegExpUseThe type that determines whether to use regular expression matching.
If not used, pass none; if used, pass use.
pattern is the regular expression pattern to compare with the text, and isMatched is a binding variable to be injected and used from the outside.
The text is checked against the regular expression whenever it is updated, and the bound variable isMatched is automatically updated.
Let's start by initializing AutoHeightEditor to check its basic functionality.
Initially, it starts with a height of 1 line, and the height dynamically increases up to a maximum of 5 lines depending on the entered text.
It detects not only line breaks caused by \n (new line characters) but also moments when the text becomes long enough to cause automatic line breaks, and reflects this in the height.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: true,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
You can determine the maximum line height by adjusting maxLine.
In the example below, we'll pass 7 to allow it to expand up to a height of 7 lines.
AutoHeightEditor(
text: $text,
maxLine: 7,
hasBorder: true,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
In the current version, values not in SwiftUI's basic Font type are not available. This is because we do a 1:1 mapping with UIFont internally to get the font size.
font and lineSpace come with the Default Values of body and 2, respectively.
If you have desired values other than these Default Values, you can inject new ones.
In the example below, we'll pass title2 and 10 to increase the font size and line spacing.
AutoHeightEditor(
text: $text,
font: .title2,
lineSpace: 10,
maxLine: 5,
hasBorder: true,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
You can decide whether to use the provided default border stroke with hasBorder.
The basic Stroke is Gray in color with a CornerRadius of 20.
In the example below, we'll set hasBorder to false to remove the border.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: false,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
You can use overlay from outside to write a custom design.
In the example below, we'll delete the default border and draw a rectangular style border with overlay.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: false,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
.overlay {
Rectangle()
.stroke()
}
isEnabled controls whether the editor receives touch events.
It is injected and used from the outside via binding.
When disabled, the text passed to disabledPlaceholder is displayed as a placeholder.
In the example below, we'll bind a variable to isEnabled and manage it with a Toggle from the outside.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: true,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
If there is no separate case for disabling, you can pass .constant() and an empty string for disabledPlaceholder.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: true,
isEnabled: .constant(true),
disabledPlaceholder: "",
regExpUse: .none)regExpUse enum determines whether to use regular expression matching.
If not used, pass none; if used, pass use.
use allows you to pass associated values pattern and isMatched.
pattern is the regular expression pattern string to be compared with the text.
isMatched is a binding variable injected and used from the outside.
In the example below, we'll pass an email pattern.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: true,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .use(
pattern: #"^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]{2,3}+$"#,
isMatched: $isMatched))
I chose not to include @FocusState within the package to keep the minimum supported version at iOS 14 rather than raising it to iOS 15.
After considering the trade-offs, I thought it more beneficial to lower the version support than to enhance usability by passing parameters.
Users with project support versions of 15.0+ can manage focus externally using FocusState.
AutoHeightEditor(
text: $text,
maxLine: 5,
hasBorder: true,
isEnabled: $isEnabled,
disabledPlaceholder: "This editor has been disabled",
regExpUse: .none)
.focused($isFocus)
The current version only supports basic dark mode adaptation by passing primary to the internal foregroundColor.
The default provided Stroke color remains fixed at gray for both light and dark modes.
AutoHeightEditor is available under the MIT license.
Please see the License for more information.
Author: Won Tae-young