UIView와 SwiftUI 통합하기

3 분 소요

SwiftUI와 UIKit의 통합

  1. 개별 UIKit 기반의 컴포넌트(UIView)를 SwiftUI View 선언부에 통합
  2. 뷰 컨트롤을 SwiftUI에 통합
  3. 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) 제거
  1. 코디네이터 클래스 구현
     class Coordinator: NSObject {
         var control: MyScrollView
            
         init(_ control: MyScrollView) {
             self.control = control
         }
            
         @objc func handleRefresh(sender: UIRefreshControl) {
             sender.endRefreshing()
         }
     }
    

    초기화 작업은 현재의 UIScrollView 인스턴스를 전달받아 로컬에 저장
    handleRefresh() 함수를 구현하여 endRefreshing() 메서드 호출

  2. 코디네이터 인스턴스 생성
     func makeCoordinator() -> Coordinator {
         Coordinator(self)
     }
    

    Coordinator 클래스의 인스턴스를 생성하고 뷰에 할당

  3. 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)
스크린샷 2022-06-15 오후 12 14 13

코디네이터 구현하기

<코디네이터 클래스 구현>

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")
    }
}

카테고리:

업데이트: