개요
UIViewRepresentable프로토콜을 사용하여 UIKit에서 사용되는 뷰들을 SwiftUI환경에서 랜더링할 수 있다.
해당 프로토콜을 채택하는 구조체는 SwiftUI에서 다른 뷰들처럼 사용될 수 있다. 프로토콜의 요구사항에 대해 우선 알아보자.
(Required) makeUIView(context:)
애 매서드는 View Object(SwiftUI 구조체)를 생성할 때 단 한번만 호출된다. 매개변수로 전달받는 context에 담긴 정보를 사용하여 반환할 뷰(UIKit view)의 초기설정을 할 수 있다.
아래코드는 구조체의 프로퍼티로 선언한 뷰들의 초기설정을 수행하고 반환하는 예시이다.
let label = UILabel()
let container = UIView()
...
func makeUIView(context: Context) -> UIView {
container.backgroundColor = .cyan
container.translatesAutoresizingMaskIntoConstraints = false
label.text = "Hello world"
label.textColor = .black
label.font = .boldSystemFont(ofSize: 17)
label.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(label)
NSLayoutConstraint.activate([
container.heightAnchor.constraint(equalToConstant: 200),
container.widthAnchor.constraint(equalToConstant: 200),
label.centerXAnchor.constraint(equalTo: container.centerXAnchor),
label.centerYAnchor.constraint(equalTo: container.centerYAnchor),
])
매개변수
- context 형식 매개변수는 UIViewRepresentable.Context타입으로 시스템의 상태를 담고 있다. 중요한 점은 coodinator라는 프로퍼티를 보유한다는 점이다. coordinator에 대해서는 아래 글에서 마저 다루도록 하겠다.
리턴타입
- UIViewRepresentable.UIViewType 타입의 인스턴스를 반환한다. 쉽게 말해 UIKit에서 사용되는 뷰를 반환 해야 한다
(Required) updateUIView(_:context:)
처음으로 생성된 경우 init, makeUIView, updateUIView순으로 호출된다.
그리고 아래의 경우들은 직접 실험을 통해 얻은 결과이다.
struct MyUIKitView: UIViewRepresentable {
// 바인딩 프로퍼티
@Binding var bindingPropery: Bool
// 일반적인 프로퍼티
let usualProperty: Bool
init(bindingPropery: Binding<Bool>, usualProperty: Bool) {
self._bindingPropery = bindingPropery
self.usualProperty = usualProperty
}
...
}
실질적 영향이 없다는 말은 해당 프로퍼티가 변뎡되어도 뷰의 형태가 변하지 않음을 의미한다.
@State를 통한 바인딩과 @Published를 통한 바인딩 설정시 같은 상황에 대해 다르게 동작한다.
3가지 매서드에 대한 호출여부를 기록하였다. (init, makeUIView, updateUIView)
1-0: 관계없는 @State 변경시
결과: 어떤 것도 재호출되지 않는다.
1-1: @State, Bidning 프로퍼티 변경시(실질적 영향X)
결과: init, updateUIView모두 호출되지 않음
1-2: @State, Bidning 프로퍼티 변경시(상태변화, backgroundColor)
결과: updateUIView만 호출된다.
1-3: @State, 일반적인 프로퍼티 변경시(실질적 영향X)
결과: 변경시점마다 init, updateUIView가 모두 호출됨
1-4: @State, 일반적인 프로퍼티 변경시(상태변화, backgroundColor)
결과: 변경시점마다 init, updateUIView가 모두 호출됨
2-1: @Published, Bidning 프로퍼티 변경시(실질적 영향X)
결과: 변경시점마다 init, updateUIView가 모두 호출됨
2-2: @Published, Bidning 프로퍼티 변경시(상태변화, backgroundColor)
결과: 변경시점마다 init, updateUIView가 모두 호출됨
2-3: @Published, 일반적인 프로퍼티 변경시(실질적 영향X)
결과: 변경시점마다 init, updateUIView가 모두 호출됨
2-4: @Published, 일반적인 프로퍼티 변경시(상태변화, backgroundColor)
결과: 변경시점마다 init, updateUIView가 모두 호출됨
2-5: @Published 프로퍼티중 관계없는 프로퍼티 업데이트시
결과: 시작 시 updateUIView가 2번호출, 관계없는 프로퍼티 업데이트시 init만 호출됨, 바인딩이나 일반전달에 상관없이 init을 호출
2-5의 경우는 다음과 같은 ObservableObject를 사용하는 경우이다.
fileprivate class ObservableClass: ObservableObject {
@Published var pubBool = false
@Published var pubBool2 = false
}
pubBool이 UIViewRepresentable과 연결되어있다. 하지만 pubBool2의 값을 변동하는 경우 init이 재호출된다.
왜냐하면 StateObject의 경우 특정 Published 프로퍼티가 하나라도 변경된다면 변화로 간주하기 때문에 Published 프로퍼티를 사용하는 모든 뷰에 영향을 준다.
그래서 Published랩퍼는 가능한 적은 양을 사용하는 것이 바람직 하겠다.
(default) sizeThatFits(_:uiView:context:)
SwiftUI의 뷰는 자신의 크기를 직접 정함으로 해당 함수가 반환하는 CGSize 인스턴스는 뷰의 크기에 직접적으로 반영된다.
아래코드는 SwiftUI로 부터 제안받은 사이즈를 UIView에 반영하는 코드이다.
func sizeThatFits(_ proposal: ProposedViewSize, uiView: WKWebView, context: Context) -> CGSize? {
guard let width = proposal.width, let height = proposal.height else { return nil }
return CGSize(width: width, height: height)
}
(default) makeCoordinator()
앞서 언급한 context매개변수는 coordinator프로퍼티를 가진다고 하였다.
makeCoordinator매서드가 반환한 객체는 coordinator로 지정되고, context매개변수를 통해 makeUIView, updateUIView매서드에 전달된다.
Coordinator?
Coordinator는 개발자가 직접 생성하는 일반적인 클래스이다. 다만 makeUIView, updateUIView에 매개변수로 전달되어 상태관리를 할 수 있다는 점에서 그 의미가 중요하다.
StoryBoard환경에서는 뷰컨트롤러가 자신이 가진 하위 뷰들의 상태를 관리할 수 있었다. UIViewRepresentable객체에서 이 기능을 대신하는 것이 Coordinator의 역할이다.
아래코드는 Coordinator를 위임자(delegate)로 선택한 UITextField예시이다.
struct UITextFieldView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> some UIView {
let tf = UITextField()
tf.delegate = context.coordinator
return tf
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
class Coordinator: NSObject, UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("압력시작")
}
}
}
기존의 위임자 역할은 보통 뷰컨트롤러가 수행한다는 점에서 Coordinator는 뷰컨트롤러의 역할을 부분적으로 대신하고 있다.
UIView의 내부 프로퍼티&매서드 조정
UIView의 내부 프로퍼티를 재정의 하여 사이징 문제를 해결할 수 있다.
- intrinsicContentSize 연산 프로퍼티
- UIView에 아무런 제약사항(constraints)을 적용하지 않았을 때 기본적으로 적용되는 사이즈(CGSize)를 계산하는 연산프로퍼티 이다. 뷰가 가지는 기본 사이즈값이다. 이 값이 정적인 값을 반환하도록 오버라이딩하면 고정된 사이즈를 설정할 수 있다.
- invalidateIntrinsicContentSize 매서드
- intrinsicContentSize이 연산하는 값을 UIView의 랜더링에 반영하는 매서드이다. 즉, 이 매서드가 호출되지 않으면 intrinsicContentSize값을 변경하여도 변화가 발생하지 않는다.
참조
'SwiftUI' 카테고리의 다른 글
[SwifUI] IgnoreSafeArea수정자 정확하게 이해하기 (2) | 2023.11.28 |
---|---|
[SwiftUI] View좌표계이해를 통한 addArc사용 (0) | 2023.09.09 |
[SwiftUI] 뷰(View)가 여러개인 경우 효율적인 애니메이션 스케쥴링 방법 (0) | 2023.08.03 |
[SwiftUI] Transition사용법 심층정리 - 주니오스의 iOS어드벤쳐 (0) | 2023.06.27 |