SwiftUIでカレンダーを実装します。
画像のような感じです。

iOS16から導入されたUICalendarView
をUIViewRepresentable
に準拠させることでカレンダーを実装しようと思います。
また、今回の実装するカレンダーの要件は以下です。
- カレンダーを表示する(UIはあまり気にしない)
- 日付タップ時に何かしらの処理を行えるようにする
UIViewRepresentable
まずSwiftUIで扱えるようにします。
struct UICalendarViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> UICalendarView {
let view = UICalendarView()
return view
}
func updateUIView(_ uiView: UICalendarView, context: Context) {
}
}
UICalendarSelectionSingleDateDelegate
日付タップを検知します。
UICalendarView
のデリゲートを使います。
struct UICalendarViewRepresentable: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UICalendarView {
let view = UICalendarView()
let selection = UICalendarSelectionSingleDate(delegate: context.coordinator)
view.selectionBehavior = selection
return view
}
func updateUIView(_ uiView: UICalendarView, context: Context) {
}
class Coordinator: NSObject, UICalendarSelectionSingleDateDelegate {
private let parent: UICalendarViewRepresentable
init(_ parent: UICalendarViewRepresentable) {
self.parent = parent
}
func dateSelection(_ selection: UICalendarSelectionSingleDate, didSelectDate dateComponents: DateComponents?) {
print("did select")
}
}
}
ViewModel
日付タップを外部のViewに通知することでViewModelでハンドリングできるようにします。
上記のdidSelectDate
デリゲートメソッドにCombineを使ってイベントを通知して、ViewModelで購読します。
func dateSelection(_ selection: UICalendarSelectionSingleDate, didSelectDate dateComponents: DateComponents?) {
parent.didSelectDateSubject.send()
}
ViewModel
private func subscribeDidSelectDate() {
didSelectDateSubject
.receive(on: DispatchQueue.main)
.sink {
print("did select")
}
.store(in: &cancellables)
}
全体のコードはこちらです。
Playgroundでそのまま実行できます。
MyPlayground.swift
import SwiftUI
import PlaygroundSupport
import Combine
struct ContentView: View {
@StateObject private var viewModel: ContentViewModel = .init()
var body: some View {
UICalendarViewRepresentable(didSelectDateSubject: viewModel.didSelectDateSubject)
}
}
class ContentViewModel: ObservableObject {
private(set) var didSelectDateSubject: PassthroughSubject<Void, Never> = .init()
private var cancellables: Set<AnyCancellable> = []
init() {
subscribeDidSelectDate()
}
private func subscribeDidSelectDate() {
didSelectDateSubject
.receive(on: DispatchQueue.main)
.sink {
print("did select")
}
.store(in: &cancellables)
}
}
struct UICalendarViewRepresentable: UIViewRepresentable {
private let didSelectDateSubject: PassthroughSubject<Void, Never>
init(didSelectDateSubject: PassthroughSubject<Void, Never>) {
self.didSelectDateSubject = didSelectDateSubject
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UICalendarView {
let view = UICalendarView()
let selection = UICalendarSelectionSingleDate(delegate: context.coordinator)
view.selectionBehavior = selection
return view
}
func updateUIView(_ uiView: UICalendarView, context: Context) {
}
class Coordinator: NSObject, UICalendarSelectionSingleDateDelegate {
private let parent: UICalendarViewRepresentable
init(_ parent: UICalendarViewRepresentable) {
self.parent = parent
}
func dateSelection(_ selection: UICalendarSelectionSingleDate, didSelectDate dateComponents: DateComponents?) {
parent.didSelectDateSubject.send()
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
参考