SwiftUI

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

하이D:) 2023. 6. 9. 11:37

앞서 SwiftUI에서 UIKit를 사용하기 위해 UIViewRepresentable 프로토콜을 사용하는 방법을 간단히 알아보았다. 이제 여기서 궁금한 것은 SwiftUI와 UIKit 간에 데이터를 주고받고 변화를 감지할 수 있는가이다. 우리는 @Binding property와 Coordinator를 사용하면 적절한 상황에서 SwiftUI와 UIKit 간에 데이터를 주고받을 수 있다.

 

 

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

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

 

 

# 🥨  @Binding property

 @Binding property를 사용하면 SwiftUI -> UIKit 방향으로 데이터를 전달할 수 있다. UIViewRepresentable 프로토콜을 채택한 구조체에서 SwiftUI로부터 @Binding property를 넘겨받았다면, 해당 프로퍼티 값이 바뀔때마다 구조체에서 정의한 updateUIView 메서드가 호출되는 것을 확인할 수 있다.

 

실제로 아래 코드는 "toggle"을 누를때마다 구조체의 updateUIView 메서드가 호출된다.

struct UIVIewRepresentablePracticeView: View {
    @State var binding : Bool = true
    @State var comment : String = "글자수를 넘기지 않았습니다."
    
    var body: some View {
        
        VStack {
            Text(comment)
            
            UIKitTextField(binding : self.$binding, comment : self.$comment)
                .frame(height : 40)

            Text("toggle")
                .onTapGesture {
                    binding.toggle()
                }
        }
    }
}
struct UIKitTextField : UIViewRepresentable {
    private let textField = UITextField()
    @Binding var binding : Bool
    @Binding var comment : String
    
    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 : ", binding)
    }
}

 

그렇다면 SwiftUI로부터받은 데이터를 바꾸고 이 바뀐 내용을 SwiftUI 쪽에 전달하려면 어떻게 해야 할까?

 @Binding property로 받은 binding이라는 변수의 값이 바뀔 때마다 updateUIView 메서드가 호출된다고 했는데, 그럼 이 함수가 호출될 때마다  @Binding property로 받은 comment라는 변수에도 변화를 줄 수 있을까? 답은 아니오이다. 아마 아래와 같이 코드를 작성해 주면 Modifying state during view update, this will cause undefined behavior 에러가 날것이다.

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<UIKitTextField>){
        comment = "----"
        print("updateUIView : ", binding)
    }

 

구조체에서 @Binding property로 받은 변수에 변화를 주고 싶을 때 필요한 것이 coordinator이다.

 

# 🥨 Coordinator

위에서 만든 뷰에서 TextField에서 적은 글자의 수가 10자를 넘겼을 때 comment 변수를 변화시켜 SwiftUI에서 해당 변수의 변동을 감지할 수 있도록 해보자.

 

우선 Coordinator를 사용하기 위해서는 아래의 세 가지를 완료해야 한다.

1) makeCoordinator 함수 생성

2) TextFieldDelegate 프로토콜을 준수하는 Coordinator 클래스 생성

3) delegate 연결해 주기

 

struct UIKitTextField : UIViewRepresentable {
    private let textField = UITextField()
    @Binding var binding : Bool
    @Binding var comment : String
    
    func makeUIView(context: UIViewRepresentableContext<UIKitTextField>) -> UITextField{
        //3) delegate 연결해주기
        //textField의 delegate
        textField.delegate = context.coordinator
        
        //textField의 style
        textField.backgroundColor = .gray
        textField.layer.cornerRadius = 3
        

        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<UIKitTextField>){
        print("updateUIView : ", binding)
    }
    
    
    //1) makeCoordinator 함수 생성
    func makeCoordinator() -> UIKitTextField.Coordinator {
        print("makeCoordinator")
        return UIKitTextField.Coordinator(self)
        
    }
    
    //2) TextFieldDelegate 프로토콜을 준수하는 Coordinator 클래스 생성
    class Coordinator: NSObject, UITextFieldDelegate {
        var parent : UIKitTextField
        init(_ textField: UIKitTextField) {
            self.parent = textField
        }
        
    }
}

 

다음으로는 UIKit에서 TextField 구현 시 많이 사용하는 UITextFieldDelegate관련 메서드를 사용하여 글자를 10자 이상 썼을 때 comment 변수를 변화시켜 보자. 위의 구조체에서 Coordinator 클래스 부분만 수정해 주면 된다. UITextFieldDelegate 프로토콜을 채택한 클래스는 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool 함수를 사용할 수 있으며, 이를 이용해서 comment 변수를 를 바꾸어줄 수 있다.

    class Coordinator: NSObject, UITextFieldDelegate {
        var parent : UIKitTextField
        init(_ textField: UIKitTextField) {
            self.parent = textField
        }
        
        func textField(_ textField: UITextField,
                       shouldChangeCharactersIn range: NSRange,
                       replacementString string: String) -> Bool {
            //            return (textField.text! + string).count <= 10
            if (textField.text! + string).count > 10 {
                parent.comment = "글자수가 초과되었습니다."
            } else {
                parent.comment = "글자수를 넘기지 않았습니다."
            }
            return true
        }
        
    }

 

실행시켜 보면 구조체에서 바꾸어준 변수가 SwiftUI에 잘 전달되어 뷰에서 적용되는 것을 볼 수 있다.