SwiftUI

SwiftUI에서 UIKit 사용하기 UIViewRepresentable (1) | TextField에서 clear button 사용하고 싶다면

하이D:) 2023. 6. 8. 18:42

SwiftUI 기반인 앱을 구현하면서 UIKit에서는 지원하지만 SwiftUI에서는 지원하지 않는 뷰들이 아직 존재한다는 것을 알게 되었다.

그럴 때 UIKit에서 제공하는 뷰를 SwiftUI에서 사용할 수 있도록 래핑 해주는 기능의 프로토콜이 바로 UIViewRepresentable이다.

 

 

SwiftUI에서 UIKit 사용하기 UIViewRepresentable (1) | TextField에서 clear button 사용하고 싶다면

SwiftUI에서 UIKit 사용하기 UIViewRepresentable(2) | @Binding 이란? Coordinator ?

 

 

# 🥨  UIViewRepresentable

SwiftUI에서 UIKit을 쓸 수 있도록 해주는 프로토콜이며, 사용 시 만들고 싶은 구조체에서 UIViewRepresentable 프로토콜을 채택해 주고 필수 함수인 makeUIView(context:) -> UIViewupdateUIView(:context:) 를 추가해 준다

 

- makeUIView(context:) -> UIView

처음으로 뷰를 생성할 때 호출하는 메서드. 여기에서는 원하는 UIView를 리턴해주면 된다. 예를 들어, UITextField를 뷰로 띄워주고 싶다면 UITextField를 리턴타입으로 지정해 주면 된다.

-  updateUIView(:context:) 

변경사항이 발생할 때마다 호출되는 메서드이다.

 

# 🥨  UIViewRepresentable 활용해서 UITextField 그리기

SwiftUI 앱을 구현하다가 UIKit의 UITextField를 활용해서 앱에 그려주어야 하는 상황이라고 가정하자. 그럴 때 UIViewRepresentable프로토콜을 채택하는 구조체를 생성해서 뷰를 그려줄 수 있다.

struct UIKitTextField : UIViewRepresentable {
    private let textField = UITextField()
    
    func makeUIView(context: UIViewRepresentableContext<UIKitTextField>) -> UITextField{
        //textField의 style
        textField.backgroundColor = .gray
        textField.layer.cornerRadius = 3
        
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<UIKitTextField>){
        print("updateUIView")
    }
}

실제 사용해 줄 때는 SwiftUI의 여느 뷰와 똑같이 사용해 주면 된다.

struct UIVIewRepresentablePracticeView: View {
	var body: some View{
        VStack {
            UIKitTextField()
                .frame(height : 40)
        }
    }
}

 

# 🥨 TextField에 clear button 생성하기

SwiftUI를 활용해서도 textField 뒤에 붙어 있는 clear button을 만들어줄 수 있겠지만, UIKit를 이용하면 코드적으로 좀 더 직관적이게 TextField에 clearButton을 붙여줄 수 있을 것 같아서 예시로 구현해 보았다.

 

SwiftUI로 TextField에 clear button 생성할 수 있는 방법은 다양하다.

방법 1)

struct UIVIewRepresentablePracticeView: View {
    @State var bindingString : String = ""
    
    func TextFieldUIKit(text: Binding<String>) -> some View{
        UITextField.appearance().clearButtonMode = .whileEditing
        return TextField("입력", text: text)
    }
    
    var body: some View { 
        VStack {
            TextFieldUIKit(text: self.$bindingString)
        }   
    }
}

방법 2) 

struct UIVIewRepresentablePracticeView: View {
    @State var bindingString : String = ""
    
    var body: some View { 
        VStack {
            TextField("NAME", text: $bindingString)
                .modifier(ClearButton(text: $bindingString))
        }   
    }
}


public struct ClearButton: ViewModifier {
    @Binding var text: String

    public init(text: Binding<String>) {
        self._text = text
    }

    public func body(content: Content) -> some View {
        HStack {
            content
            Spacer()
            Image(systemName: "multiply.circle.fill")
                .foregroundColor(.secondary)
                .opacity(text == "" ? 0 : 1)
                .onTapGesture { self.text = "" }
        }
    }
}

 

UIViewRepresentable 사용해서 UIKit으로 UITextField에 clearButton 생성

방법 1)

UITextField의 appearance()메서드를 사용해서 텍스트필드에 글자를 입력 중일 때 clear button이 나올 수 있도록 설정해준다,

struct UIKitTextField : UIViewRepresentable {
    private let textField = UITextField()
    
    func makeUIView(context: UIViewRepresentableContext<UIKitTextField>) -> UITextField{
        //textField의 style
        textField.backgroundColor = .gray
        textField.layer.cornerRadius = 3
        
        UITextField.appearance().clearButtonMode = .whileEditing
        
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<UIKitTextField>){
        print("updateUIView")
    }
 }

 

방법 2)

UIButton을 따로 생성해주고 textField.rightView를 사용해서 버튼을 넣어주는 방법이 있다. 이는 보통 해당 버튼을 눌렀을 때 추가적인 특정 동작을 원할 때 사용할 수 있으며, #selector를 만들어서 clearButton.addTarget 내부에 넘겨주면된다.

 

struct UIKitTextField : UIViewRepresentable {
    private let textField = UITextField()
    let helper : Helper = Helper()
    
    class Helper {
        public var onClickCancelButton: (() -> Void)?
        @objc func clickCancelButton() {
            onClickCancelButton?()
            print("onClickClearButton")
        }
    }
    
    func makeUIView(context: UIViewRepresentableContext<UIKitTextField>) -> UITextField{
        //textField의 style
        textField.backgroundColor = .gray
        textField.layer.cornerRadius = 3
        
        //textField 우측에 UIButton으로 clearButton 삽입
        let btnImage = UIImage(systemName: "multiply")
        let clearButton = UIButton(type: .custom)
        clearButton.setImage(btnImage, for: .normal)
        clearButton.tintColor = UIColor.lightGray
        
        helper.onClickCancelButton = {
            textField.text = ""
        }
        clearButton.addTarget(helper, action: #selector(helper.clickCancelButton), for: .touchUpInside)
        
        textField.rightView = clearButton
        textField.rightViewMode = .always
        
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<UIKitTextField>){
        print("updateUIView")
    }
 }

 

 

TextField에 clear button을 넣어주는것을 예제로 기본적인 UIViewRepresentable 사용 방법을 알아보았다. 이것 외에도UIViewRepresentable을 사용할 때 SwiftUI -> UIKit 방향으로 데이터 전달하는 방법, UIKit -> SwiftUI 방향으로 데이터 전달하는 방법이 있다. 전자는 @Binding property를 활용하는 방법이고, 후자는 Coordinator를 활용하는 방법이다. 이렇게 데이터 방향에 대한 이해를 2편에서 알아 볼 것이다.