SwiftUI Tutorials - Interfacing with UIKit
UIKitとの連携方法です。画面遷移するためのUIPageViewController()をSwiftUIで使うために必要なことをやっていきます。
Create a View to Represent a UIPageViewController
UIKitのViewをSwiftUIで使う場合はUIViewControllerRepresentableプロトコルに準拠させることが必要です。
まず新規ファイルPageViewController.swift作り、UIViewControllerRepresentableプロトコルに準拠させます。
import SwiftUI
import UIKit
struct PageViewController<Page: View>: UIViewControllerRepresentable {
var pages: [Page]
}
ページの元になるデータは配列に入れていきます。
UIViewControllerRepresentableで必要とされる要件を追加します。
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[UIHostingController(rootView: pages[0])], direction: .forward, animated: true)
}
続きをする前に、ダウンロードしたResources directoryより画像をXcodeに入れておきます。
Landmark.swiftを編集します。
var featureImage: Image? {
isFeatured ? Image(imageName + "_feature") : nil
}
を追加します。
次にFeatureCard.swiftを新規に作りSwiftUIファイルを作成し構造体FeatureCardを作ります。
struct FeatureCard: View {
var landmark: Landmark
var body: some View {
landmark.featureImage?
.resizable()
.aspectRatio(3 / 2, contentMode: .fit)
.overlay(TextOverlay(landmark: landmark))
}
}
struct TextOverlay: View {
var landmark: Landmark
var gradient: LinearGradient {
LinearGradient(
gradient: Gradient(
colors: [Color.black.opacity(0.6), Color.black.opacity(0)]),
startPoint: .bottom,
endPoint: .center)
}
var body: some View {
ZStack(alignment: .bottomLeading) {
Rectangle().fill(gradient)
VStack(alignment: .leading) {
Text(landmark.name)
.font(.title)
.bold()
Text(landmark.park)
}
.padding()
}
.foregroundColor(.white)
}
}
新規ファイルPageView.swiftを作り構造体PageViewでViewを作ります。
struct PageView<Page: View>: View {
var pages: [Page]
var body: some View {
PageViewController(pages: pages)
}
}
Create the View Controller’s Data Source
スワイプしてページを変更できるようにしていきます。
PageViewController.swiftを編集していきます。
SwiftUIでUIKitを表示するために
class Coordinator: NSObject {
var parent: PageViewController
init(_ pageViewController: PageViewController) {
parent = pageViewController
}
}
を定義します。
struct PageViewController<Page: View>: UIViewControllerRepresentable {
var pages: [Page]
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIPageViewController {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal)
pageViewController.dataSource = context.coordinator
return pageViewController
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[context.coordinator.controllers[0]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource {
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 makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
pageViewController.setViewControllers(
[context.coordinator.controllers[0]], direction: .forward, animated: true)
}
class Coordinator: NSObject, UIPageViewControllerDataSource {
var parent: PageViewController
var controllers = [UIViewController]()
init(_ pageViewController: PageViewController) {
parent = pageViewController
controllers = parent.pages.map { UIHostingController(rootView: $0) }
}
以上のコードを追加すると、PageView.swiftを実行してプレビューしてみると、画面遷移、画面をスワイプすることで切り替わるようになります。
Track the Page in a SwiftUI View’s State
View間でデータを追跡することができるように@State、@Bindingを付けた変数を宣言します。
PageViewController.swiftを編集していきます。まず変数宣言です。この変数でページ番号で表示する画面を決定します。
@Binding var currentPage: Int
そして、func updateUIViewController()を編集します。setViewControllersを更新することで@Bindingされた値を渡します。
pageViewController.setViewControllers(
[context.coordinator.controllers[currentPage]], direction: .forward, animated: true)
PageView.swiftを編集していきます。
struct PageView<Page: View>: View {
var pages: [Page]
@State private var currentPage = 0
var body: some View {
PageViewController(pages: pages, currentPage: $currentPage)
}
}
@State private var currentPage = 0
PageViewController(pages: pages, currentPage: $currentPage)
を追加して currentPage の初期値、
@State private var currentPage = 0
を設定しています。そしてテキストを追加するためにコードを追加します。
var body: some View {
VStack {
PageViewController(pages: pages, currentPage: $currentPage)
Text("Current Page: \(currentPage)")
}
}
Text("Current Page: \(currentPage)")
次にプロトコルUIPageViewControllerDelegateに適合させます。
class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {}
必要なメソッドを追加します。ページ読み込み時にアニメーションを切り替えるたびに呼び出すために
parent.currentPage = index
とcurrentPageを更新します。
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 makeUIViewController(context: Context) -> UIPageViewController {}
に
pageViewController.delegate = context.coordinator
を追加します。
Add a Custom Page Control
SwiftUIで組み上げられているCategoryHome.swiftにUIKitにUIViewRepresentableを適応させたものを入れ込みます。
UIViewRepresentable
同じライフサイクルで、UIViewControllerRepresentable
まず、新しいファイル、PageControl.swiftに以下のように UIViewRepresentableに適合させた構造体を作ります。必要なメソッドを書いていきます。
func makeUIView(context: Context)
func updateUIView
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
return control
}
func updateUIView(_ uiView: UIPageControl, context: Context) {
uiView.currentPage = currentPage
}
}
次にPageView.swiftを編集します。
var body: some View {
ZStack(alignment: .bottomTrailing) {
PageViewController(pages: pages, currentPage: $currentPage)
PageControl(numberOfPages: pages.count, currentPage: $currentPage)
.frame(width: CGFloat(pages.count * 18))
.padding(.trailing)
}
ZStack()
PageControl(numberOfPages: pages.count, currentPage: $currentPage)
.frame(width: CGFloat(pages.count * 18))
.padding(.trailing)
を修正しています。
次にPageControl.swiftに再度戻って、ユーザーがページを切り替えられるようにしていきます。
新しいメソッド
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
と、新しいCoordinatorを作ります。
class Coordinator: NSObject {
var control: PageControl
init(_ control: PageControl) {
self.control = control
}
@objc
func updateCurrentPage(sender: UIPageControl) {
control.currentPage = sender.currentPage
}
}
そしてもう一つ
func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
control.numberOfPages = numberOfPages
control.addTarget(
context.coordinator,
action: #selector(Coordinator.updateCurrentPage(sender:)),
for: .valueChanged)
return control
}
control.addTarget(
context.coordinator,
action: #selector(Coordinator.updateCurrentPage(sender:)),
for: .valueChanged)
最後にCategoryHome.swiftに以下追加します。
List {
PageView(pages: modelData.features.map { FeatureCard(landmark: $0) })
.aspectRatio(3 / 2, contentMode: .fit)
.listRowInsets(EdgeInsets())
PageView(pages: modelData.features.map { FeatureCard(landmark: $0) })
.aspectRatio(3 / 2, contentMode: .fit)
新しい PageView()を追加します。全体です。
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
@State private var showingProfile = false
var body: some View {
NavigationView {
List {
PageView(pages: modelData.features.map { FeatureCard(landmark: $0) })
.aspectRatio(3 / 2, contentMode: .fit)
.listRowInsets(EdgeInsets())
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
.listRowInsets(EdgeInsets())
}
.listStyle(InsetListStyle())
.navigationTitle("Featured")
.toolbar {
Button(action: { showingProfile.toggle() }) {
Image(systemName: "person.crop.circle")
.accessibilityLabel("User Profile")
}
}
.sheet(isPresented: $showingProfile) {
ProfileHost()
.environmentObject(modelData)
}
}
}
}
この記事が気に入ったらサポートをしてみませんか?