본문 바로가기

iOS/SwiftUI

SwiftUI에서 UITextField 커스텀하여 사용하기

💫 SwiftUI의 TextField의 사용

우리가 보통 사용하는 SwiftUI의 TextField컴포넌트를 사용하는 방법

import SwiftUI

struct ContentView: View {
    @State private var text = ""
    
    var body: some View {
        VStack {
            Text("Text: \(text)")
                .frame(maxWidth: .infinity, alignment: .leading)
            
            TextField("TextField", text: $text)
        }
    }
}

 

 

SwiftUI의 기본 제공 TextField는 쓰기에는 무난하지만 여러 delegate를 사용해 커스텀 해야하는 상황이 오면 결국 UITextField를 커스텀 해 사용하는 경우가 있다.

이 방법은 TextField뿐 아니라 대부분의 UIKit의 컴포넌트들을 비슷한 방법으로 커스텀하여 사용할 수 있다.

 

💫 UITextField를 SwiftUI의 컴포넌트로 커스텀하기

import SwiftUI

// MARK: - TextFieldRepresentable

struct TextFieldRepresentable: UIViewRepresentable { // 1️⃣
    private let placeholder: String
    @Binding var text: String
    
    init(_ placeholder: String, text: Binding<String>) {
        self.placeholder = placeholder
        self._text = text
    }
    
    func makeUIView(context: Context) -> UITextField { // 2️⃣
        let textField = UITextField()
        textField.placeholder = placeholder
        textField.text = text
        textField.delegate = context.coordinator // 3️⃣
        
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) { // 4️⃣
        uiView.text = text
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
    }
    
    func makeCoordinator() -> Coordinator { // 5️⃣
        Coordinator(text: $text)
    }
    
    class Coordinator: NSObject { // 6️⃣
        @Binding var text: String
        
        init(text: Binding<String>) {
            self._text = text
        }
    }
}

extension TextFieldRepresentable.Coordinator: UITextFieldDelegate { // 7️⃣
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let currentValue = textField.text as NSString? {
            let newValue = currentValue.replacingCharacters(in: range, with: string)
            self.text = newValue
        }
        return true
    }
}

 

1️⃣ : UIKit의 컴포넌트는 UIViewRepresentable 프로토콜을 채택해 구현할 수 있다.

2️⃣ : UIViewRepresentable의 필수 구현 메소드, 생성할 뷰를 리턴하는 부분으로 이니셜라이징할 코드를 작성하면 된다.

3️⃣ : UITextField에 6️⃣에서 구현한 클래스의 delegate를 주입

4️⃣ : UIViewRepresentable의 필수 구현 메소드, 뷰가 업데이트 될 때마다 실행되는 부분

5️⃣ : UIViewRepresentable의 옵셔널 메소드로 6️⃣에서 구현한 클래스를 생성한다.

6️⃣ : 이 구조체에서 사용할 Coordinator를 구현해준다.

7️⃣ : Coordinator에 커스텀할 delegate를 주입하여 사용하면 된다.

 

💫 최종코드 및 비교영상

import SwiftUI

struct ContentView: View {
    @State private var text = ""
    
    var body: some View {
        VStack {
            Text("Text: \(text)")
                .frame(maxWidth: .infinity, alignment: .leading)
            
            TextField("TextField", text: $text)
            
            TextFieldRepresentable("TextFieldRepresentable", text: $text)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


// MARK: - TextFieldRepresentable

struct TextFieldRepresentable: UIViewRepresentable {
    private let placeholder: String
    @Binding var text: String
    
    init(_ placeholder: String, text: Binding<String>) {
        self.placeholder = placeholder
        self._text = text
    }
    
    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.placeholder = placeholder
        textField.text = text
        textField.delegate = context.coordinator
        
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }
    
    class Coordinator: NSObject {
        @Binding var text: String
        
        init(text: Binding<String>) {
            self._text = text
        }
    }
}

extension TextFieldRepresentable.Coordinator: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let currentValue = textField.text as NSString? {
            let newValue = currentValue.replacingCharacters(in: range, with: string)
            self.text = newValue
        }
        return true
    }
}