ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자동 문자 결합 방지하기
    iOS 2023. 11. 30. 09:57

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 iOS 개발 시 자동 문자 결합을 방지해보는 구현을 한번 삽질해보겠습니다 🙋🏻

     


    어쩌다 이걸 포스팅하게 되었지?

    일단 사건의 전말은 이러합니다.

    텍스트필드가 주어지고 최대 글자수는 8글자까지만 받으려고 합니다.

    그렇기에 텍스트 필드에서 입력되어 만들어진 텍스트 String의 count가 총 8을 넘는지 판단해야겠죠?

     

    자, 아래와 같은 코드가 있다고 가정해봅시다.

     

    import UIKit
    
    class ViewController: UIViewController, UITextFieldDelegate {
      
      let textField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
      let button = UIButton(frame: CGRect(x: 20, y: 150, width: 100, height: 50))
      
      override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.borderStyle = .roundedRect
        textField.delegate = self
        self.view.addSubview(textField)
        
        button.setTitle("8글자 초과!", for: .normal)
        button.backgroundColor = .blue
        button.isHidden = true
        self.view.addSubview(button)
      }
      
      func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let newText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
        button.isHidden = newText.count < 9
        return true
      }
    }

     

    해당 간단한 코드를 보시면, 텍스트필드에서 텍스트를 입력받는데 UITextFieldDelegate를 채택하고 해당 메서드를 통해서 입력이 변할때마다 로직을 돌아줍니다.

     

    로직은 단순히 현재 입력된 텍스트의 카운트가 8을 넘어가면 8글자 초과! 라는 버튼을 노출시키는것이죠.

     

    그럼 우선 정상적인지 간단히 한번 돌려볼까요?

     

     

    코드 실행 결과는 아주 정상적입니다.

    8글자 초과하면 버튼이 노출되죠?

    내부적으로 해당 String은 Character의 집합이기에 각 "123456789"를 ["1","2",3"...."9"]로 인식하여 count를 구하면 9가 나오기 때문입니다.

     

    그럼 어떤 상황에서 문제가 발생할 수 있을까요?

     

     

    보셨나요?

    .을 하나의 문자로 인식해야하는데 정상적이지 않죠?

    .이 세개가 연속으로 쓰이게 되면 우리가 기대하는건 [".", ".", "."] 인 Character 형태로 들어와야 하는데,

    실제로 해당 . 세개가 연속이 되면 자동 문자 결합을 내부 시스템에서 시켜 ["..."]로 하나의 말줄임 문자로 자동 결합하여 변환되어 인식해버리는것입니다.

     

    이건 비단 .만 그런것이 아니라 -도 그러합니다.

    .는 3개를 하나의 말줄임 문자로 결합하지만 -는 두개가 되면 em 대시로 인식해버려서 동일하게 한 문자로 인식합니다.

     

    여기서 발생하는 개념이 자동 문자 결합입니다.

     

    물론 자동 문자 결합 기능이 디폴트로 되어 있는 의도도 있고 유용하게 쓰일 수 있지만 각자 개발하는 요구사항에서 핏이 안맞을 수 있어요 🥲

    예를들어, 회원가입을 할때 닉네임은 8글자 이하까지만 설정하게하는 로직이 들어갔을때 ...을 한문자로 인식하니 실제로는 "...그린...그린..."와 같은 닉네임도 충분히 가능한것이죠.

     

    이건 클라에서만의 문제에서 벗어나 서버와 닉네임 String을 전달하고 사용할때 이 정책이 다르기에 예기치 못한 사이드이펙트를 발생시킬 수 있습니다.

     

    그래서! 오늘 원한다면 이 자동 문자 결합을 오프해보는 포스팅을 해볼거에요ㅎㅎ

     

    먼저 자동 문자 결합에 대해 간단히 알아보고 갈까요?


    자동 문자 결합이란?

    Automatic Text Replacement라고도 부르는 이 자동 문자 결합은 특정 문자나 문자열을 다른 문자나 문자열로 자동 변환하는 기능을 의미합니다.

    자동으로 문장의 첫글자를 대문자로 변환하거나 잘못 입력된 단어를 수정하기도 하는 역할을 하죠.

    또한, 위 예시에서 발생하는 포인트였던 .이나 -와 같은 문자들이 연속해서 들어오면 말줄임 문자나 em 대시 문자 하나로 변환되는 기능도 해줍니다.

     

    그렇기에 이 기능이 어떻게 보면 사용성에서 기능에 따라 더 편리하게 사용될 수도 있지만 위 상황과 같이 자동 변환되기에 원하지 않는 결과를 만들어낼 가능성도 농후하죠 🥲

     

    그럼 이거 어떻게 오프해볼 수 있을까요?

     

    우선 UIKit 환경에서 코드를 적용해볼까요?


    UIKit에서 자동 문자 결합 방지하기

    UIKit UITextField에 smartDashesType이라는 속성을 사용할 수 있습니다.

    import UIKit
    
    class ViewController: UIViewController, UITextFieldDelegate {
      
      let textField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
      let button = UIButton(frame: CGRect(x: 20, y: 150, width: 100, height: 50))
      
      override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.borderStyle = .roundedRect
        textField.delegate = self
        
        🙋🙋🙋🙋추가🙋🙋🙋🙋
        textField.smartDashesType = .no
        🙋🙋🙋🙋🙋🙋🙋🙋🙋
        
        self.view.addSubview(textField)
        
        button.setTitle("8글자 초과!", for: .normal)
        button.backgroundColor = .blue
        button.isHidden = true
        self.view.addSubview(button)
      }
      
      func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let newText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
        button.isHidden = newText.count < 9
        return true
      }
    }

     

    해당 속성을 no로 설정하면 스마트 대시를 비활성화 할 수 있습니다.

     

    smartDashesType를 타고 들어가면 이러한걸 마주할 수 있어요.

     

    @MainActor public protocol UITextInputTraits : NSObjectProtocol {
      ...
      
      @available(iOS 11.0, *)
      optional var smartDashesType: UITextSmartDashesType { get set }
      
      ...
    }

     

    UITextSmartDashesType이죠.

     

    그럼 이 UITextSmartDashesType 타입을 공식문서에서 알아볼까요?

     

    enum UITextSmartDashesType : Int, @unchecked Sendable

     

    정의는 이러합니다.

    하이픈과 en 대시, em 대시 사이 자동 변환 동작을 지정해주는 상수입니다.

     

    여기서 en 대시는 -로 보통 범위를 표현할때 사용되죠.

    우리가 흔히 사용하는 - 하나가 en 대시입니다.

     

    반면 em 대시는 —로 -가 두개 결합되어 문장 내 생략,중단,강조를 나타날때 사용된다고 합니다.

     

    그렇기에 en 대시와 em 대시가 엄연히 다른것이죠!

     

    enum 타입이기에 3가지 케이스가 존재해요.

     

    1️⃣ `default` -> 기본 스마트 대시 기능을 사용

    2️⃣ no -> 스마트 대시 기능을 비활성화

    3️⃣ yes -> 스마트 대시 기능을 활성화

     

    그렇기에 기본적으로 스마트 대시 기능을 활성화되어 있기에 우리가 처음 마주했던 문제였던 .과 -에 대해 연속적으로 들어오면 하나의 문자로 자동 변환이 되었던것이죠.

     

    그럼 위에 해당 값을 no로 설정했으니 정상적으로 자동 변환이 방지되는지 확인해볼까요?

     

     

    이제 .가 연속적으로 3개 들어와도 ...로 말줄임 한 문자로 변환되지 않고 각각의 "." 문자로 인식되죠!

     

    그럼 UIKit말고 SwiftUI에서는 어떻게 해볼 수 있을까요?


    SwiftUI에서 자동 문자 결합 방지하기

    우선 결론부터 말해보자면, SwiftUI에서 기본적으로 UIKit의 smartDashesType과 동일한 동작을 하는건 못찾았습니다 🥲

     

    TextField에서 disableAutocorrection이라는 뷰 모디파이어가 존재하긴 하지만 해당 기능은 자동 수정을 비활성화하는 속성이지 자동 문자 결합 기능을 비활성화 하는 뷰 모디파이어는 아니기에 실제로 적용이 되지 않더라구요..

     

    또 다른 방법으로 찾아봤던것이 textContentType이라는 속성도 텍스트 필드에서 사용할 수 있는데 적절하진 않더라구요.

     

    그렇기에 SwiftUI에서 제공하는것만으로는 어려울것 같습니다 😭

    만약 방법을 알고 계신다면 같이 공유해주시면 감사하겠습니다 🙇🏻

     

    그래서 결국 UIKit을 또 끌어들여와야합니다.

     

    한번 코드로 구현해볼까요?

     

    import SwiftUI
    
    struct ContentView: View {
      @State var text = ""
      
      var body: some View {
        CustomTextField(text: $text)
          .frame(height: 40)
          .padding()
        
        if text.count > 8 {
          Button(
            action: { },
            label: {
              Text("8글자 초과!")
            }
          )
        }
      }
    }
    
    struct CustomTextField: UIViewRepresentable {
      @Binding var text: String
      
      func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.borderStyle = .roundedRect
        textField.smartDashesType = .no
        textField.delegate = context.coordinator
        return textField
      }
      
      func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
      }
      
      func makeCoordinator() -> Coordinator {
        Coordinator(self)
      }
      
      class Coordinator: NSObject, UITextFieldDelegate {
        var parent: CustomTextField
        
        init(_ textField: CustomTextField) {
          self.parent = textField
        }
        
        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
          if let text = textField.text, let textRange = Range(range, in: text) {
            parent.text = text.replacingCharacters(in: textRange, with: string)
          }
          return false
        }
      }
    }

     

    이렇게 UIKit을 사용하여 커스텀 텍스트 필드를 만들어서 SwiftUI에서 연동해 사용해야 합니다.

    그럼 UIKit에서 제공하는 smartDashesType을 사용할 수 있게 되죠!

     

     

    그럼 이렇게 UIKit에서 동작하는것처럼 의도한것처럼 자동 문자 결합을 방지할 수 있죠 🙋🏻

     


    마무리

    이렇게 자동 문자 결합이 무엇인지 UIKit과 SwiftUI에서 어떻게 이 기능을 방지할 수 있는지 코드로도 확인해봤습니다ㅎㅎ

    SwiftUI에서도 기본적으로 제공해주면 좋으련만....

    제가 못찾은것일수도 있는데 그랬으면 좋겠네요 🙏🏻

     


    레퍼런스

     

    textContentType | Apple Developer Documentation

    The semantic meaning for a text input area.

    developer.apple.com

     

    disableAutocorrection(_:) | Apple Developer Documentation

    Sets whether to disable autocorrection for this view.

    developer.apple.com

     

     

    UITextSmartDashesType | Apple Developer Documentation

    Constants that specify the automatic conversion behavior between hyphens and en or em dashes.

    developer.apple.com

     

Designed by Tistory.