UIViewControllerRepresentable
UIViewControllerRepresentable는 UIViewController를 SwiftUI 에서 사용가능하게 해주는 프로토콜이다.
이 프로토콜을 채택하게 되면
func makeUIViewController(context: Self.Context) -> Self.UIViewControllerType
func updateUIViewController(Self.UIViewControllerType, context: Self.Context)
이 두 메서드를 필수적으로 구현 해야 한다.
makeUIViewController(context:) 메서드는 UIKit 뷰 컨트롤러를 생성하고 초기화하는 역할을 한다. SwiftUI가 화면에 뷰를 처음 표시할 때 단 한 번만 호출되며, 이 메서드는 뷰 컨트롤러를 생성하고 반환하는 책임만을 가진다.
updateUIViewController(_:context:) 메서드는 SwiftUI의 상태가 변경될 때마다 뷰 컨트롤러의 상태를 업데이트하는 역할을 한다. SwiftUI 뷰의 @State나 @Binding과 같은 프로퍼티가 변경되면, SwiftUI는 이 메서드를 호출해 UIKit 뷰 컨트롤러에 최신 데이터를 전달할 수 있도록 해준다.
여기서 추가적으로 Coordinator 라는 개념이 있는데 Coordinator는 SwiftUI 뷰와 연동되는 UIKit 뷰 컨트롤러의 대리자(delegate) 역할을 하는 클래스다. 한 마디로, SwiftUI와 UIKit 사이의 소통을 원활하게 만들어주는 다리라고 할 수 있다 !
Coordinator 를 사용하면, Cocoa에서 자주 쓰이는 패턴들을 구현할 수 있다. 예를 들어, delegate 패턴, data source 패턴, 그리고 target-action을 통한 사용자 이벤트 처리 같은 것들이 있다.
쇼츠 형태의 페이지 뷰 만들기
파일 구성은 이렇게 해주었다.

PageViewController
import SwiftUI
import UIKit
struct PageViewController<Page: View>: UIViewControllerRepresentable {
var pages: [Page]
@Binding var currentPage: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .vertical)
pageViewController.dataSource = context.coordinator
pageViewController.delegate = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[context.coordinator.controllers[currentPage]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var parent: PageViewController
var controllers = [UIViewController]()
init(_ pageViewController: PageViewController) {
parent = pageViewController
controllers = parent.pages.map { UIHostingController(rootView: $0) }
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController?
{
guard let index = controllers.firstIndex(of: viewController) else {
return nil
}
if index == 0 {
return controllers.last
}
return controllers[index - 1]
}
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController?
{
guard let index = controllers.firstIndex(of: viewController) else {
return nil
}
if index + 1 == controllers.count {
return controllers.first
}
return controllers[index + 1]
}
func pageViewController(
_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
if completed,
let visibleViewController = pageViewController.viewControllers?.first,
let index = controllers.firstIndex(of: visibleViewController) {
parent.currentPage = index
}
}
}
}
func makeCoordinator()
이 메서드에서는 Coordinator 인스턴스를 생성하고 반환한다. Coordinator는 UIPageViewController의 델리게이트와 데이터 소스 역할을 하며, UIKit에서 발생한 이벤트를 SwiftUI로 전달하는 중요한 역할을 한다.
makeUIViewController(context:)
이 메서드는 UIPageViewController 인스턴스를 처음 생성하고 초기 설정하는 역할을 한다. SwiftUI가 이 뷰를 화면에 처음 띄울 때 단 한 번만 호출된다.
transitionStyle: .scroll: 페이지 넘김 애니메이션 스타일을 스크롤로 설정한다.
navigationOrientation: .vertical: 페이지 넘김 방향을 수직으로 설정한다.
pageViewController.dataSource = context.coordinator: UIPageViewController의 데이터 소스를 위에서 만든 Coordinator 인스턴스로 지정한다. 데이터 소스는 뷰 컨트롤러에 어떤 뷰를 표시할지 알려주는 역할을 한다.
pageViewController.delegate = context.coordinator: UIPageViewController의 델리게이트를 Coordinator 인스턴스로 지정한다. 델리게이트는 페이지 넘김이 완료되었을 때와 같은 이벤트를 처리한다.
updateUIViewController(_:context:)
이 메서드는 SwiftUI의 currentPage 값이 변경될 때마다 호출되어, UIPageViewController의 현재 표시 페이지를 업데이트 한다.
pageViewController.setViewControllers(...): UIPageViewController에게 특정 뷰 컨트롤러를 표시하도록 지시한다. 여기서는 context.coordinator.controllers 배열에서 currentPage에 해당하는 뷰 컨트롤러를 가져와 보여준다.
-> UIPageViewController의 델리게이트와 데이터 소스 역할을 수행하는 핵심 클래스다.
var parent: PageViewController: 부모인 PageViewController 구조체에 대한 참조를 가진다. 이를 통해 @Binding으로 선언된 currentPage와 같은 프로퍼티에 접근할 수 있다.
var controllers = [UIViewController](): parent.pages 배열의 각 SwiftUI 뷰를 UIHostingController를 사용해 UIViewController로 변환하여 저장한다. UIHostingController는 SwiftUI 뷰를 UIKit 환경에서 사용할 수 있게 해주는 래퍼 역할을 한다.
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController)
현재 페이지의 이전 페이지에 해당하는 UIViewController를 반환한다. 이 메서드를 통해 이전 페이지로 스와이프할 때 어떤 뷰를 보여줄지 결정한다. 페이지가 첫 번째일 경우 마지막 페이지를 반환하여 무한 순환 스크롤을 구현한다.
func pageViewController(
_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController)
현재 페이지의 다음 페이지에 해당하는 UIViewController를 반환한다. 마찬가지로 마지막 페이지일 경우 첫 번째 페이지를 반환한다.
func pageViewController(
_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool)
페이지 넘김 애니메이션이 완료되었을 때 호출되는 델리게이트 메서드다.
if completed, ...: 애니메이션이 성공적으로 완료되었을 때만 실행된다.
let visibleViewController = pageViewController.viewControllers?.first: 현재 화면에 보이는 뷰 컨트롤러를 가져온다.
let index = controllers.firstIndex(of: visibleViewController): 보이는 뷰 컨트롤러의 인덱스를 찾는다.
parent.currentPage = index: 찾은 인덱스를 부모 뷰의 @Binding 변수인 currentPage에 할당한다. 이 부분이 바로 UIKit에서 SwiftUI로 데이터가 다시 전달되는 핵심적인 과정이다.
PageView
import SwiftUI
struct PageView<Page: View>: View {
var pages: [Page]
@State private var currentPage = 0
var body: some View {
PageViewController(pages: pages, currentPage: $currentPage)
}
}
PageViewControllerApp
import SwiftUI
@main
struct PageViewControllerApp: App {
var body: some Scene {
WindowGroup {
PageView(pages: [
Color.green,
Color.orange,
Color.purple
])
}
}
}
실제 구현 영상

https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable
https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit