Rodhos Soft

備忘録を兼ねた技術的なメモです。Rofhos SoftではiOSアプリ開発を中心としてAndroid, Webサービス等の開発を承っております。まずはご相談下さい。

UIKitとの連携

チュートリアルより。 UIPageViewControllerをSwiftUIで使う。

まず、使いたいUIViewControllerをwrapするクラスを作る。 そのクラスはUIViewControllerRepresentableを実装する。

import Foundation
import SwiftUI
import UIKit

struct PageViewController: UIViewControllerRepresentable {
    
    typealias UIViewControllerType = UIPageViewController
    
    var controllers:[UIViewController]

    // ページ情報
    @Binding var currentPage:Int
    
  // 関連するdataSorce, delegate等のクラスはCoordinaterとして渡す。
    func makeCoordinator() -> Coordinater {
        Coordinater(self)
    }
    
 // ここでViewControllerを作って渡す。delegate等もcontextを通じてつなげる。
    func makeUIViewController(context contetxt:Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .
            horizontal)
        pageViewController.dataSource = contetxt.coordinator
        pageViewController.delegate = contetxt.coordinator
        return pageViewController
    }
    
// 更新
    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers([controllers[currentPage]], direction: .forward, animated: true)
    }
    
// DataSorce, Delegate等はCoordinaterとして宣言する。
    class Coordinater: NSObject, UIPageViewControllerDataSource,UIPageViewControllerDelegate {
        var parent:PageViewController
        init(_ pageViewController:PageViewController) {
            self.parent = pageViewController
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last;
            }
            return parent.controllers[index - 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                           return nil
                       }
                       if index + 1 == parent.controllers.count {
                           return parent.controllers.first
                       }
                       return parent.controllers[index + 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed, let visibleViewController = pageViewController.viewControllers?.first,
                let inedx = parent.controllers.firstIndex(of:visibleViewController) {
                parent.currentPage = inedx
            }
        }
        
        
    }
}

UIViewも似た感じで扱える。UIViewRepresentableを実装する。

import SwiftUI
import UIKit

struct PageControl: UIViewRepresentable {
    var numberOfPages:Int
    @Binding var currentPage:Int
    
    func makeUIView(context:Context) -> UIPageControl {
        let control = UIPageControl()
        control.numberOfPages = numberOfPages
       // controlのtarget, actionをつないでいる。
        control.addTarget(context.coordinator, action: #selector(Coordinator.updateCurrentPage(sender:)), for:  .valueChanged)
        return control
    }
    
    // 表示ロジック用のクラスを作る。
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
  // 更新
    func updateUIView(_ uiView: UIPageControl, context: UIViewRepresentableContext<PageControl>) {
        uiView.currentPage = currentPage
    }
    
    class Coordinator: NSObject {
        var control: PageControl
        
        init(_ control:PageControl) {
            self.control = control
        }
        
        @objc func updateCurrentPage(sender:UIPageControl) {
            control.currentPage = sender.currentPage
        }
    }

}

これらのプロトコルを実装すれば普通のビューのように扱える。

import SwiftUI

struct PageView<Page:View>: View {
/// UIPageViewControllerではUIViewController達として扱うのでSwiftUIのViewをUIHostViewControllerでくるんで扱う。
    var viewControllers:[UIHostingController<Page>]
    @State var currentPage = 0
    init(_ views:[Page])  {
        self.viewControllers = views.map {
            UIHostingController(rootView: $0)
        }
    }
    
    var body: some View {
            ZStack(alignment: .bottomTrailing){
                PageViewController(controllers: viewControllers, currentPage: $currentPage)
                PageControl(numberOfPages: viewControllers.count, currentPage: $currentPage)
                    .padding(.trailing)
            }
    }
}

struct PageView_Previews: PreviewProvider {
    static var previews: some View {
        PageView(features.map { FeatureCard(landmark: $0)})
            .aspectRatio(3/2, contentMode: .fit)
    }
}