この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
SwiftUIでUIPageViewController
のようなページめくりのViewを作成した際に、そのPageControl
の色を1ページ毎変更する方法を調べました。
環境
- Xcode 14
ページめくりのViewを作成する
SwiftUIでUIPageViewController
のようなページめくりを表現するには、TabView
とiOS 14から使用できる.tabViewStyle
にPageTabViewStyle
を組み合わせることによって簡単に表現することが出来ます。
struct ContentView: View {
@State private var selection = 0
var body: some View {
TabView(selection: $selection) {
Group {
Text("0")
.tag(0)
Text("1")
.tag(1)
Text("2")
.tag(2)
}
.font(.largeTitle)
.foregroundColor(.white)
}
.background(.black)
.tabViewStyle(.page(indexDisplayMode: .automatic))
}
}
PageTabViewSyle
.tabViewStyle
にPageTabViewSyle
を設定することでページスクロールするTabView
を表現することが出来ます。
また、.page(indexDisplayMode:)
でPageTabViewStyle.IndexDisplayMode
を指定すると、インデックスの表示モードを変更出来ます。
PageTabViewStyle.IndexDisplayMode
- always
- ページ数に関係なく常にインデックス ビューを表示する
- automatic
- 複数のページがある場合はインデックス ビューを表示します
- never
- インデックス ビューを表示しない
PageControlのcurrentPageIndicatorTintColorがデフォルトで白という問題
簡単にページめくりの表現は出来たのですが、PageControl
のcurrentPageIndicatorTintColor
がデフォルトで白色という問題が出てきました。
背景が白色のViewの場合はPageControl
がほとんど見えないです。
PageControlの色を変更する
調べたところ、現状ではPageControl
の色を変更するには、UIPageControl
のUIAppearance
の値を直接変更する必要がありそうでした。
struct ContentView: View {
@State private var selection = 0
// 初期化時にUIPageControlの色を変更
init() {
let currenTintColor = UIColor.black
UIPageControl.appearance().currentPageIndicatorTintColor = currenTintColor
UIPageControl.appearance().pageIndicatorTintColor = currenTintColor.withAlphaComponent(0.2)
}
var body: some View {
TabView(selection: $selection) {
Group {
Text("0")
.tag(0)
Text("1")
.tag(1)
Text("2")
.tag(2)
}
.font(.largeTitle)
}
.tabViewStyle(.page(indexDisplayMode: .automatic))
}
}
初期化時に色を変更
init() {
let currenTintColor = UIColor.black
UIPageControl.appearance().currentPageIndicatorTintColor = currenTintColor
UIPageControl.appearance().pageIndicatorTintColor = currenTintColor.withAlphaComponent(0.2)
}
初期化時にUIPageControl.appearance()
に変更したい色を渡すことでUIPageControl
の色を変更出来ました。
ただ問題点としては、UIPageControl.appearance()
の値を変更すると、アプリ全体のUIPageControl
の色に影響を与えてしまいます。
PageControlの色を1ページ毎変更する
UIPageControl
のUIAppearance
を変更する方法を用いて、1ページ毎に色を変更する方法を試してみました。
SelectionTypeを作成
先ほどまではselection
にInt
型を使用していましたが、選択されたものによって、foregroundColor
とbackgroundColor
を変更したかったのでSelectionType
というenum
を作成しました。
enum SelectionType: Int, CaseIterable, Identifiable {
case first = 0
case second = 1
case third = 2
var id: Int {
return self.rawValue
}
var foregroundColor: Color {
switch self {
case .first, .third:
return .white
case .second:
return .black
}
}
var backgroundColor: Color {
switch self {
case .first, .third:
return .black
case .second:
return .white
}
}
}
SelectionViewを作成
渡されたSelectionType
によってUIPageControl.appearance()
を変更したかったのでカスタムViewを作成しました。
struct SelectionView: View {
init(type: SelectionType) {
self.number = type.id
// UIPageControlの色を変更する
let currentPageIndicatorTintColor = UIColor(type.foregroundColor)
UIPageControl.appearance().currentPageIndicatorTintColor = currentPageIndicatorTintColor
UIPageControl.appearance().pageIndicatorTintColor = currentPageIndicatorTintColor.withAlphaComponent(0.2)
}
let number: Int
var body: some View {
Text(String(number))
}
}
ContentView
selectionType
によって、foregroundColor
とbackgroundColor
を変更しています。
struct ContentView: View {
@State private var selectionType: SelectionType = .first
var body: some View {
TabView(selection: $selectionType) {
ForEach(SelectionType.allCases) { type in
SelectionView(type: type)
.tag(type)
.font(.largeTitle)
.foregroundColor(type.foregroundColor)
}
}
.tabViewStyle(.page(indexDisplayMode: .automatic))
.background(selectionType.backgroundColor)
}
}
しかし、init()
で値を更新する方法では、Bindingで色を変更しているわけではない為、背景色が白の際もcurrentPageIndicatorTintColor
が白のままになっていて、よく見えない結果になりました。
対応策
PageControl
部分を自作して、SelectionType
によって色を変更します。
SectionPageControl
struct SelectionPageControl: UIViewRepresentable {
@Binding var type: SelectionType
let numberOfPages: Int
func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
// PageControl上に表示するページ数を指定する
control.numberOfPages = numberOfPages
// PageControlの現在のページ
control.currentPage = type.id
let currentIndicatorTintColor = UIColor(type.foregroundColor)
control.currentPageIndicatorTintColor = currentIndicatorTintColor
control.pageIndicatorTintColor = currentIndicatorTintColor.withAlphaComponent(0.2)
// PageControl自体をタップされてもPageControl自体が動作しないようにする
control.isUserInteractionEnabled = false
return control
}
func updateUIView(_ uiView: UIPageControl, context: Context) {
// PageControlの現在のページ
uiView.currentPage = type.id
let currentIndicatorTintColor = UIColor(type.foregroundColor)
uiView.currentPageIndicatorTintColor = currentIndicatorTintColor
uiView.pageIndicatorTintColor = currentIndicatorTintColor.withAlphaComponent(0.2)
}
}
selectionType
をBindingして、その値の変更をcurrentPageIndicatorTintColor
に適用しています。
不要になったUIPageControl.appearanceを消す
struct SelectionView: View {
init(type: SelectionType) {
self.number = type.id
// 不要になった為、消す
// let currentPageIndicatorTintColor = UIColor(type.foregroundColor)
// UIPageControl.appearance().currentPageIndicatorTintColor = currentPageIndicatorTintColor
// UIPageControl.appearance().pageIndicatorTintColor = currentPageIndicatorTintColor.withAlphaComponent(0.2)
}
let number: Int
var body: some View {
Text(String(number))
}
}
ContentViewに自作PageControlを設置する
struct ContentView: View {
@State private var selectionType: SelectionType = .first
var body: some View {
// VStackで囲む
VStack {
TabView(selection: $selectionType) {
ForEach(SelectionType.allCases) { type in
SelectionView(type: type)
.tag(type)
.font(.largeTitle)
.foregroundColor(type.foregroundColor)
}
}
// TabView側のPageControlを.neverに変更
.tabViewStyle(.page(indexDisplayMode: .never))
// 自作PageControlを設置
SelectionPageControl(type: $selectionType,
numberOfPages: SelectionType.allCases.count)
}
// VStackのbackgroundを変更
.background(selectionType.backgroundColor)
}
}
SelectionPageControl
を設置して、ページ数とバインディングするSelectionType
を渡します。TabView
側のPageControl
は不要になる為、indexDisplayMode
を.never
に変更します。- 自作
PageControl
を画面下部に配置する為にVStack
でTabView
とSelectionPageControl
を囲みます VStack
のbackgroundColor
を希望のカラーの変更します。
結果
無事にページ毎にPageControl
の色を変更することが出来ました!
おわりに
今回はTabView
を使う前提でUIPageControl
を自作する方法で対応しましたが、UIPageViewController
をUIViewControllerRepresentable
で表現する方法もあるかなと思いました。
できれば力技で表現する方法は避けたいのでTabView
のindexDisplay
のtintColor
を気軽に変更できるようなAPIが追加される日を楽しみにしております。
より良い変更方法がありましたら、優しく教えていただけると幸いです。