UIView와 SwiftUI 통합하기
SwiftUI와 UIKit의 통합
- 개별 UIKit 기반의 컴포넌트(UIView)를 SwiftUI View 선언부에 통합
- 뷰 컨트롤을 SwiftUI에 통합
- SwiftUI 뷰를 기존의 UIKit 기반 코드에 통합
UIView를 SwiftUI와 통합하기
사용자 인터페이스를 구성하는 컴포넌트(버튼, 레이블, 텍스트 관련 뷰, 지도, 슬라이드 등..)
-> UIView 클래스의 하위 클래스
UI 컴포넌트를 SwiftUI에 통합하려면
UIViewRepresentable 프로토콜을 구현하는 구조체로 래핑
- 필수 메서드 구현
- makeUIView() - UIView 기반 컴포넌트의 인스턴스를 생성, 필요한 초기화 작업을 수행한 뒤 반환
- updateView() - UIView 자체를 업데이트해야 하는 변경이 SwiftUI 뷰에서 생길 때마다 호출
- 선택 메서드 구현
- dismantleUIView() - 뷰를 제거하기 전에 정리 작업을 할 수 있는 기회 제공
[ SwiftUI + UILabel 뷰 ]
import SwiftUI
struct MyUILabel: UIViewRepresentable {
var text: String
func makeUIView(context: UIViewRepresentableContext<MyUILabel>)
-> UILabel {
let myLabel = UILabel()
myLabel.text = text
return myLabel
}
func updateUIView(_ uiView: UILabel,
context: UIViewRepresentableContext<MyUILabel>) {
// 필요한 업데이트 수행
}
}
struct MyUILabel_Previews: PreviewProvider {
static var previews: some View {
MyUILabel(text: "Hello")
}
}
UILabel 뷰 래핑
struct ContentView: View {
var body: some View {
VStack {
MyUILabel(text: "Hello UIKit")
}
}
}
UILabel 뷰는 이벤트 처리가 필요없는 정적 컴포넌트
이벤트에 반응해야 하는 뷰들은 코디네이터(coordinator)를 구현
Coordinator 추가하기
코디네이터(coordinator)
- 래핑된 UIView 컴포넌트에 필요한 프로토콜과 핸들러 메서드를 구현하는 클래스의 형태
- 코디네이터 클래스의 인스턴스는 makeCoordinator() 메서드를 통해 래퍼에 적용
[ SwiftUI + UIScrollView 뷰 ]
- 뷰의 맨 위를 넘어서 스크롤하려고 할 때 리프레시 컨트롤을 추가 가능
- 리프레시 컨트롤(UIRefreshControl):
- 스피닝 프로그래스 인디케이터(spinning progress indicator)를 표시
- 최신 콘텐트로 뷰를 업데이트
- 리프레시가 완료되면 UIRefreshControl 인스턴스의 endRefreshing() 메서드 호출
-> 프로그래스 스피너(progress spinner) 제거
- 코디네이터 클래스 구현
class Coordinator: NSObject { var control: MyScrollView init(_ control: MyScrollView) { self.control = control } @objc func handleRefresh(sender: UIRefreshControl) { sender.endRefreshing() } }
초기화 작업은 현재의 UIScrollView 인스턴스를 전달받아 로컬에 저장
handleRefresh() 함수를 구현하여 endRefreshing() 메서드 호출 - 코디네이터 인스턴스 생성
func makeCoordinator() -> Coordinator { Coordinator(self) }
Coordinator 클래스의 인스턴스를 생성하고 뷰에 할당
- UIScrollView 인스턴스 생성
func makeUIView(context: Context) -> UIScrollView { let srollView = UIScrollView() // UIScrollView 인스턴스 생성 scrollView.refreshConrol = UIRefreshControl() // UIRefreshControl 구성 scrollView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged) // UIRefreshControl 인스턴스에 값이 변경될 때 handleRefresh() 메서드 호출 return scrollView }
UIKit 델리게이션과 데이터 소스 처리하기
델리게이션(Delegation)
- UIKit의 기능
- 어떤 객체가 하나 이상의 작업을 수행하는 책임을 다른 객체로 전달
- 래핑된 UIView에 의해 처리되는 이벤트의 경우 추가적인 작업
- EX) UIScrollView - 스크롤을 하고 있는지, 맨 위로 스크롤했을 때 알림
- UIScrollViewDelegate 프로토콜 준수
- 이벤트가 발생시 자동으로 호출되는 메서드 구현(콜백 메서드)
데이터 소스(Data Source)
- 화면에 표시될 데이터를 UIView 기반의 컴포넌트에 제공하는 객체
- EX) UITableView - 테이블 내에 표시될 셀들을 제공
- UITableViewDataSource 프로토콜 준수
class Coordinator: NSObject, UIScrollViewDelegate {
var control: MyScrollView
init(_ control: MyScrollView) {
self.control = control
}
func scrollViewDidScroll(_ scrollView: UIScrollView) { // 콜백 메서드
// 사용자가 현재 스크롤 중
}
@objc func handleRefresh(sender: UIRefreshControl) {
sender.endRefreshing()
}
}
사용자가 현재 스크롤을 하고 있다는 알림을 수신
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
// 코디네이터 인스턴스에 접근하여 UIScrollView 인스턴스의 델리게이트로 추가
.
.
}
사용자가 스크롤을 하게 되면 scrollViewDidScroll 메서드가 반복해서 호출
예제 프로젝트
UIScrollView 래핑하기
UIScrollView를 래핑하기 위해 UIViewRepresentable 프로토콜 사용
struct MyScrollView: UIViewRepresentable {
var text: String
func makeUIView(context: UIViewRepresentableContext<MyScrollView>)
-> UIScrollView {
let scrollView = UIScrollView()
scrollView.refreshControl = UIRefreshControl()
let myLabel = UILabel(frame:
CGRect(x: 0, y: 0, width: 300, height: 50))
myLabel.text = text
scrollView.addSubview(myLabel)
return scrollView
}
func updateUIView(_ uiView: UIScrollView,
context: UIViewRepresentableContext<MyScrollView>) {
}
}
struct MyScrollView_Previews: PreviewProvider {
static var previews: some View {
MyScrollView(text: "Hello World")
}
}
리프레시 컨트롤이 나타남
스크롤을 멈춰도 리프레시 인디케이터가 멈추질 않음 -> 이벤트 처리 필요(Coordinator)
코디네이터 구현하기
<코디네이터 클래스 구현>
func updateUIView(_ uiView: UIScrollView,
context: UIViewRepresentableContext<MyScrollView>) {
}
class Coordinator: NSObject, UIScrollViewDelegate {
var control: MyScrollView
init(_ control: MyScrollView) {
self.control = control
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("View is Scrolling")
}
@objc func handleRefresh(sender: UIRefreshControl) {
sender.endRefreshing()
}
}
<코디네이터를 델리게이트로 추가>
func makeUIView(context: UIViewRepresentableContext<MyScrollView>)
-> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.refreshControl = UIRefreshControl()
let myLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
myLabel.text = text
scrollView.addSubview(myLabel)
scrollView.refreshControl?.addTarget(context.coordinator,
action: #selector(Coordinator.handleRefresh),
for: .valueChanged)
// 리프레시 컨트롤에 대한 타켓으로 handleRefresh() 메서드 추가 - 인디케이터가 멈추게
return scrollView
}
<코디네이터 인스턴스 생성>
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
MyScrollView 사용하기
ContentView 수정
struct ContentView: View {
var body: some View {
MyScrollView(text: "UIView in SwiftUI")
}
}