Skip to content

wontaeyoung/AutoHeightEditor

Repository files navigation

Platform Deployments UseFor SPM License Github

Read English Translation


AutoHeightEditor는 Dynamic Height 기능이 있는 커스텀 TextEditor 라이브러리입니다.


제작 배경

이 라이브러리는 제가 프로젝트에 필요해서 직접 구현하게 된 커스텀 TextEditor입니다.

제가 진행하고 있는 프로젝트에서 동적으로 높이가 조절되는 입력 인터페이스가 요구사항이었는데, iOS 16부터는 TextFieldaxis 파라미터를 통해 Dynamic Height로 동작하는 입력 인터페이스를 쉽게 사용할 수 있습니다.

하지만 프로젝트 최소 지원버전이 iOS 15.0+로 결정되었고, 여러 줄의 텍스트 입력을 받기 위해서는 TextEditor를 사용해야했습니다.

기본 API로 제공되는 TextEditor를 사용해보신분들은 공감하시겠지만 지원하는 기능이 TextField에 비해 부족하고, 특히 별도로 높이를 지정해주지 않으면 차지할 수 있는 최대 높이를 가지게 됩니다.

이를 해결하기 위해서 적절한 높이를 동적으로 계산해주는 AutoHeightEditor를 커스텀으로 제작하게 되었습니다.

자세한 제작 배경 및 구현 과정은 블로그에서 확인할 수 있습니다.



라이브러리 소개

AutoHeightEditor는 기본적으로 Dynamic Height이 가장 큰 특징입니다.

이를 구현하기 위해 폰트 높이, 행간, 텍스트 길이, 개행문자를 통해서 적절한 높이로 TextEditor의 높이를 실시간으로 변경합니다.


주요 로직

  1. 텍스트에서 \n(개행문자)의 갯수를 계산합니다.
  2. TextEditor의 가로 길이와 입력된 텍스트의 길이를 계산해서 자동 줄바꿈이 몇 번 일어나야하는지 계산합니다.
  3. 1번과 2번을 합쳐서 총 줄바꿈 횟수를 계산합니다.
  4. 폰트 크기, 행간, 줄바꿈 수를 계산하여 TextEditor의 총 높이를 계산합니다.

사용자 편의

최소 1줄 ~ maxLine까지 사용자의 입력에 따라 높이가 변경되고 최대 라인 수, 사용되는 폰트, 행간, 활성화 여부와 같은 선택사항을 파라미터로 전달받아서 반영합니다.

개인적으로 사용하는 컴포넌트를 라이브러리에 맞춰서 수정한만큼, 제가 아닌 다른 사용자가 사용하는 환경을 고려하여 아래 사항들을 추가했습니다.

  • isEnabled를 바인딩 받아서 외부에서 활성화 여부 관리 가능
  • 고정으로 존재하는 Border 스트로크 사용 여부 선택 가능
  • Disabled 안내 문구 커스텀 가능
  • 전달받은 정규식 매치 여부를 계산해서 바인딩 된 Bool 변수에 반영

제작 배경에서 설명한 것처럼 iOS 16은 아직은 실무에 적용하기 부담스러운 버전이기 때문에, 이를 고려하여 TextEditor가 처음 나온 iOS 14부터 사용 가능하도록 구현했습니다.



파라미터 리스트

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>

에디터에 바인딩되는 입력 텍스트 문자열입니다. 외부에서 바인딩으로 주입해서 사용합니다.



font: Font

텍스트에 적용할 폰트 타입입니다. Default Value로 body가 주입되고, 원하는 다른 폰트가 있다면 주입해서 사용 가능합니다.



lineSpace: CGFloat

텍스트 라인 사이에 들어가는 행 간격입니다. Default Value로 2가 주입되고, 원하는 다른 값이 있다면 주입해서 사용 가능합니다.



maxLine: Int

에디터의 높이가 증가하는 상한선 라인 수입니다. 입력 라인이 늘어날 때 maxLine까지 에디터 높이가 증가하고, 그 이후로는 늘어나지 않습니다.



hasBorder: Bool

기본으로 제공되는 Stroke의 사용 여부를 결정합니다. 기본 Stroke는 Gray 컬러에 20의 CornerRadius 값을 가지고 있습니다.



isEnabled: Binding<Bool>

에디터의 활성화 여부를 결정합니다. 외부에서 바인딩으로 주입하고, 조절해서 사용합니다.



disabledPlaceholder: String

에디터가 비활성화 되어있을 때, 사용자에게 안내하기 위한 문구입니다.



public enum RegExpUse {
    case use(pattern: String, isMatched: Binding<Bool>)
    case none
}

regExpUse: RegExpUse

정규식 매치 여부를 사용하는지를 결정하는 타입입니다.

사용하지 않으면 none, 사용한다면 use를 전달합니다.

pattern은 매칭에 사용할 정규식 패턴, isMatched는 외부에서 주입하고 활용할 바인딩 값입니다.

텍스트가 업데이트 될 때마다 정규식을 검사해서 isMatched에 전달된 바인딩 변수를 자동으로 업데이트합니다.



사용 가이드

우선 기본적인 동작을 확인해보기 위해 AutoHeightEditor를 초기화 해보겠습니다.

처음에는 1줄 높이로 시작하고, 입력된 텍스트에 따라 최대 5줄까지 높이가 동적으로 늘어납니다.

\n(개행문자)로 일어나는 줄바꿈 뿐만 아니라, 텍스트가 길어져서 자동으로 줄바꿈이 발생하는 순간도 감지해서 높이에 반영합니다.

AutoHeightEditor(
    text: $text,
    maxLine: 5,
    hasBorder: true,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)



최대 라인 수 수정하기

maxLine을 조정해서 최대 높이 라인 수를 결정할 수 있습니다.

아래 예시에서는 7을 전달해서 7줄 높이까지 늘어나도록 해보겠습니다.

AutoHeightEditor(
    text: $text,
    maxLine: 7,
    hasBorder: true,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)



폰트와 행 간격 수정하기

현재 버전에서는 SwiftUI의 기본 Font 타입에 없는 값은 사용이 불가합니다. 폰트의 사이즈를 구하기 위해 내부에서 UIFont와 1:1 매핑을 하기 때문입니다.

fontlineSpace에는 기본값으로 body2가 전달되고 있습니다.

원하는 값이 있다면 Default Value 대신에 새로운 값을 전달할 수 있습니다.

아래 예시에서는 title210을 전달해서 폰트 사이즈를 키우고 행 간격도 넓혀보겠습니다.

AutoHeightEditor(
    text: $text,
    font: .title2,
    lineSpace: 10,
    maxLine: 5,
    hasBorder: true,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)



Border Stroke 커스텀

hasBorder를 통해 기본으로 제공되는 테두리 사용 여부를 결정할 수 있습니다.

기본 Stroke는 Gray 컬러에 20의 CornerRadius 값을 가지고 있습니다.

아래 예시에서는 hasBorder의 값을 false로 전달하여 테두리를 삭제해보겠습니다.

AutoHeightEditor(
    text: $text,
    maxLine: 5,
    hasBorder: false,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)



외부에서 overlay를 사용하여 원하는 디자인을 커스텀으로 작성할 수 있습니다.

아래 예시에서는 기본 테두리를 삭제하고, overlay로 사각형 스타일의 테두리를 그려보겠습니다.

AutoHeightEditor(
    text: $text,
    maxLine: 5,
    hasBorder: false,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)
.overlay {
    Rectangle()
        .stroke()
}



에디터 사용 활성화 관리

isEnabled로 에디터의 터치 이벤트 수신 여부를 조정할 수 있습니다.

외부에서 관리하기 위해 바인딩으로 전달받아 사용합니다.

disabled되면 disabledPlaceholder에 전달된 텍스트를 플레이스홀더로 표시합니다.

아래 예시에서는 isEnabled에 변수를 바인딩하고, Toggle로 외부에서 관리해보겠습니다.

AutoHeightEditor(
    text: $text,
    maxLine: 5,
    hasBorder: �true,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)



만약에 따로 비활성화 하는 경우가 없다면 .constant()로 전달하고, disabledPlaceholder에는 빈 문자열을 전달하면 됩니다.

AutoHeightEditor(
    text: $text,
    maxLine: 5,
    hasBorder: true,
    isEnabled: .constant(true),
    disabledPlaceholder: "",
    regExpUse: .none)



정규식 사용

regExpUse 열거형으로 정규식 사용 여부를 결정할 수 있습니다.

사용하지 않는다면 none, 사용한다면 use를 주입하면 됩니다.

use에는 연관값으로 patternisMatched를 전달할 수 있습니다.

pattern 텍스트와 비교할 정규식 패턴 문자열입니다.

isMatched는 외부에서 바인딩 받는 변수로, 내부에서 정규식 일치 여부를 업데이트 받습니다.

아래 예시에서는 이메일 패턴을 전달해보겠습니다.

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))



포커스 관리

@FocusState를 패키지 내부에 포함시키면 최소 지원버전이 iOS 15로 올라가기 때문에 포함시키지 않았습니다.

트레이드 오프를 생각해봤을 때, 파라미터로 전달받는 사용성보다 지원 버전을 낮추는게 더 메리트가 있다고 생각했습니다.

프로젝트 지원 버전이 15.0+인 사용자분들은 외부에서 FocusState를 사용해서 포커스를 관리할 수 있습니다.

AutoHeightEditor(
    text: $text,
    maxLine: 5,
    hasBorder: true,
    isEnabled: $isEnabled,
    disabledPlaceholder: "This editor has been disabled",
    regExpUse: .none)
.focused($isFocus)



다크모드 지원

현재 버전에서는 내부 foregroundColor에서 primary를 전달해서 기본적인 다크모드 대응만 지원하고 있습니다.

기본 제공 Stroke 색상은 라이트 / 다크 모두 gray 고정입니다.



라이센스

AutoHeightEditor는 MIT 라이센스의 범위 내에서 사용 가능합니다.

자세한 정보는 라이센스에서 확인해주세요.


작성자: 원태영

About

SwiftUI TextEditor API with dynamic height by text line

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages