3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftUIでカレンダーを実装する

Posted at

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

iOS16から導入されたUICalendarViewUIViewRepresentableに準拠させることでカレンダーを実装しようと思います。

また、今回の実装するカレンダーの要件は以下です。

  1. カレンダーを表示する(UIはあまり気にしない)
  2. 日付タップ時に何かしらの処理を行えるようにする

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())

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?