Monthly calender sheet in SwiftUI - swiftui

How can you show a monthly calendar sheet (similiar to the one in the Calendar app) to select a single date in SwiftUI?
Something like this:

Including the CVCalendar component via the Swift Package manager and wrapping it as UIViewRepresentable does the trick:
For example:
import CVCalendar
import SwiftUI
import UIKit
/// CalendarView is a SwiftUI wrapper for the [CVCalendar](https://github.com/CVCalendar/CVCalendar) component
struct CalendarView: UIViewRepresentable {
#Binding var date: Date
func makeUIView(context: Context) -> CVCalendarView {
let view = CVCalendarView(frame: CGRect(x: 0, y: 0, width: 350, height: 350))
view.calendarDelegate = context.coordinator
view.commitCalendarViewUpdate()
view.setContentHuggingPriority(.required, for: .horizontal)
return view
}
func updateUIView(_ uiView: CVCalendarView, context: Context) {
uiView.toggleViewWithDate(self.date)
}
func makeCoordinator() -> Coordinator {
Coordinator(date: self.$date)
}
class Coordinator: NSObject, CVCalendarViewDelegate {
#Binding var date: Date
init(date: Binding<Date>) {
self._date = date
}
func presentationMode() -> CalendarMode {
.monthView
}
func firstWeekday() -> Weekday {
Weekday(rawValue: Calendar.current.firstWeekday)!
}
func presentedDateUpdated(_ date: CVDate) {
if let date = date.convertedDate() {
self.date = date
}
}
}
}
struct CalendarView_Previews: PreviewProvider {
#State static var date = Date()
static var previews: some View {
CalendarView(date: $date)
}
}

Related

Can't drag down/dismiss UIColorPickerViewController

I am displaying a UIColorPickerViewController as a sheet using the sheet() method, everything works fine but I can't drag down/dismiss the view anymore.
import Foundation
import SwiftUI
struct ColorPickerView: UIViewControllerRepresentable {
private var selectedColor: UIColor!
init(selectedColor: UIColor) {
self.selectedColor = selectedColor
}
func makeUIViewController(context: Context) -> UIColorPickerViewController {
let colorPicker = UIColorPickerViewController()
colorPicker.selectedColor = self.selectedColor
return colorPicker
}
func updateUIViewController(_ uiViewController: UIColorPickerViewController, context: Context) {
// Silent
}
}
.sheet(isPresented: self.$viewManager.showSheet, onDismiss: {
ColorPickerView()
}
Any idea how to make the drag/down dismiss gesture works?
Thanks!
Ran into the same problem when trying to build a color picker similar to above. What worked was "wrapping" the color picker in a view with a Dismiss button. And also discovered that the bar at the top of the view would allow the picker to now be dragged down and away. Below is my wrapper. (One could add more features such as a title to the bar.)
struct ColorWrapper: View {
var inputColor: UIColor
#Binding var isShowingColorPicker: Bool
#Binding var selectedColor: UIColor?
var body: some View {
VStack {
HStack {
Spacer()
Button("Dismiss", action: {
isShowingColorPicker = false
}).padding()
}
ColorPickerView(inputColor: inputColor, selectedColor: $selectedColor)
}
}
}
And for completeness, here is my version of the color picker:
import SwiftUI
struct ColorPickerView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIColorPickerViewController
var inputColor: UIColor
#Binding var selectedColor: UIColor?
#Environment(\.presentationMode) var isPresented
func makeUIViewController(context: Context) -> UIColorPickerViewController {
let picker = UIColorPickerViewController()
picker.delegate = context.coordinator
picker.supportsAlpha = false
picker.selectedColor = inputColor
return picker
}
func updateUIViewController(_ uiViewController: UIColorPickerViewController, context: Context) {
uiViewController.supportsAlpha = false
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIColorPickerViewControllerDelegate {
var parent: ColorPickerView
init(parent: ColorPickerView) {
self.parent = parent
}
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
parent.isPresented.wrappedValue.dismiss()
}
func colorPickerViewController(_ viewController: UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) {
parent.selectedColor = color
// parent.isPresented.wrappedValue.dismiss()
}
}
}

How to use function from other struct/view in SwiftUI?

Newbie SwiftUI Dev here.
I want to create a scheduling app in SwiftUI and I would like to create a button in navigation bar which change calendar's scope.
From .week to month and return.
struct HomeVC: View {
init() {
navbarcolor.configureWithOpaqueBackground()
navbarcolor.backgroundColor = .systemGreen
navbarcolor.titleTextAttributes = [.foregroundColor: UIColor.white]
navbarcolor.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().standardAppearance = navbarcolor
UINavigationBar.appearance().scrollEdgeAppearance = navbarcolor
}
#State private var selectedDate = Date()
var body: some View {
NavigationView{
VStack{
CalendarRepresentable(selectedDate: $selectedDate)
.frame(height: 300)
.padding(.top, 15)
Spacer()
ListView()
}
.navigationBarTitle("Calendar")
.toolbar {
Button(action: {
switchCalendarScope()
}) {
Text("Toggle")
}
}
}
}
}
This is my calendar struct, and I would like to take from here the switchCalendarScope function, and use it into button's action, but doesn't work.
struct CalendarRepresentable: UIViewRepresentable{
typealias UIViewType = FSCalendar
#Binding var selectedDate: Date
var calendar = FSCalendar()
func switchCalendarScope(){
if calendar.scope == FSCalendarScope.month {
calendar.scope = FSCalendarScope.week
} else {
calendar.scope = FSCalendarScope.month
}
}
func updateUIView(_ uiView: FSCalendar, context: Context) { }
func makeUIView(context: Context) -> FSCalendar {
calendar.delegate = context.coordinator
calendar.dataSource = context.coordinator
calendar.allowsMultipleSelection = true
calendar.scrollDirection = .vertical
calendar.scope = .week
//:Customization
calendar.appearance.headerTitleFont = UIFont.systemFont(ofSize: 25, weight: UIFont.Weight.heavy)
calendar.appearance.weekdayFont = .boldSystemFont(ofSize: 15)
calendar.appearance.weekdayTextColor = .black
calendar.appearance.selectionColor = .systemGreen
calendar.appearance.todayColor = .systemGreen
calendar.appearance.caseOptions = [.headerUsesUpperCase, .weekdayUsesUpperCase]
calendar.appearance.headerTitleColor = .black
return calendar
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, FSCalendarDelegate, FSCalendarDataSource {
var parent: CalendarRepresentable
var formatter = DateFormatter()
init(_ parent: CalendarRepresentable) {
self.parent = parent
}
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
return 0
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
formatter.dateFormat = "dd-MM-YYYY"
print("Did select == \(formatter.string(from: date))")
}
func calendar(_ calendar: FSCalendar, didDeselect date: Date, at monthPosition: FSCalendarMonthPosition) {
formatter.dateFormat = "dd-MM-YYYY"
print("Did de-select == \(formatter.string(from: date))")
}
}
}
Can anybody help?
You don't need to trigger the function in your UIViewRepresentable. You simply need to declare a variable in there that is the representation of the selected scope, and pass that in with your initializer. I am going to assume that your scope variable is of Type Scope for this:
struct CalendarRepresentable: UIViewRepresentable {
typealias UIViewType = FSCalendar
#Binding var selectedDate: Date
var calendar = FSCalendar()
var scope: Scope
func updateUIView(_ uiView: FSCalendar, context: Context) { }
func makeUIView(context: Context) -> FSCalendar {
calendar.delegate = context.coordinator
calendar.dataSource = context.coordinator
calendar.allowsMultipleSelection = true
calendar.scrollDirection = .vertical
// Set scope here
calendar.scope = scope
//:Customization
...
return calendar
}
...
}
Then from the HomeVC view you would call it like this:
CalendarRepresentable(selectedDate: $selectedDate, scope: scope)
The view will get recreated as needed. Also, one last thing, in SwiftUI there are no ViewControllers. Your HomeVC should just be named Home. It is the view, not a view controller, and they work differently and take a different mental model. This is why you were struggling in solving this. Even the UIViewRepresentable is a view in the end, and it just wraps a ViewController and instantiates the view. And they are all structs; you don't mutate a struct, you simply recreate it when you need to change it.

Adding Next/ Prev buttons to FSCalendar in SwiftUI

I've been playing around with FSCalendar and it's helped me build my own customized calendar.
Because it's written in UIKit, I've had a couple of problems integrating it to my SwiftUI project, such as adding a Next and Previous button to the sides of the calendar.
This is what I have so far:
ContentView, where I used an HStack to add the buttons to the sides of my calendar
struct ContentView: View {
let myCalendar = MyCalendar()
var body: some View {
HStack(spacing: 5) {
Button(action: {
myCalendar.previousTapped()
}) { Image("back-arrow") }
MyCalendar()
Button(action: {
myCalendar.nextTapped()
}) { Image("next-arrow") }
}
}}
And the MyCalendar struct which, in order to integrate the FSCalendar library, is a UIViewRepresentable.
This is also where I added the two functions (nextTapped and previousTapped) which should change the displayed month when the Buttons are tapped:
struct MyCalendar: UIViewRepresentable {
let calendar = FSCalendar(frame: CGRect(x: 0, y: 0, width: 320, height: 300))
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> FSCalendar {
calendar.delegate = context.coordinator
calendar.dataSource = context.coordinator
return calendar
}
func updateUIView(_ uiView: FSCalendar, context: Context) {
}
func nextTapped() {
let nextMonth = Calendar.current.date(byAdding: .month, value: 1, to: calendar.currentPage)
calendar.setCurrentPage(nextMonth!, animated: true)
print(calendar.currentPage)
}
func previousTapped() {
let previousMonth = Calendar.current.date(byAdding: .month, value: -1, to: calendar.currentPage)
calendar.setCurrentPage(previousMonth!, animated: true)
print(calendar.currentPage)
}
class Coordinator: NSObject, FSCalendarDelegateAppearance, FSCalendarDataSource, FSCalendarDelegate {
var parent: MyCalendar
init(_ calendar: MyCalendar) {
self.parent = calendar
}
func minimumDate(for calendar: FSCalendar) -> Date {
return Date()
}
func maximumDate(for calendar: FSCalendar) -> Date {
return Date().addingTimeInterval((60 * 60 * 24) * 365)
}
}}
This is what it looks like in the simulator:
As you can see, I've managed to print the currentPage in the terminal whenever the next or previous buttons are tapped, but the currentPage is not changing in the actual calendar.
How could I fix this?
As you are using UIViewRepresentable protocol for bind UIView class with SwiftUI. Here you have to use ObservableObject - type of object with a publisher that emits before the object has changed.
You can check the code below for the resulting output: (Edit / Improvement most welcomed)
import SwiftUI
import UIKit
import FSCalendar
class CalendarData: ObservableObject{
#Published var selectedDate : Date = Date()
#Published var titleOfMonth : Date = Date()
#Published var crntPage: Date = Date()
}
struct ContentView: View {
#ObservedObject private var calendarData = CalendarData()
var strDateSelected: String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale.current
return dateFormatter.string(from: calendarData.selectedDate)
}
var strMonthTitle: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM yyyy"
dateFormatter.locale = Locale.current
return dateFormatter.string(from: calendarData.titleOfMonth)
}
var body: some View {
VStack {
HStack(spacing: 100) {
Button(action: {
self.calendarData.crntPage = Calendar.current.date(byAdding: .month, value: -1, to: self.calendarData.crntPage)!
}) { Image(systemName: "arrow.left") }
.frame(width: 35, height: 35, alignment: .leading)
Text(strMonthTitle)
.font(.headline)
Button(action: {
self.calendarData.crntPage = Calendar.current.date(byAdding: .month, value: 1, to: self.calendarData.crntPage)!
}) { Image(systemName: "arrow.right") }
.frame(width: 35, height: 35, alignment: .trailing)
}
CustomCalendar(dateSelected: $calendarData.selectedDate, mnthNm: $calendarData.titleOfMonth, pageCurrent: $calendarData.crntPage)
.padding()
.background(
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.white)
.shadow(color: Color.black.opacity(0.2), radius: 10.0, x: 0.0, y: 0.0)
)
.frame(height: 350)
.padding(25)
Text(strDateSelected)
.font(.title)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CustomCalendar: UIViewRepresentable {
typealias UIViewType = FSCalendar
#Binding var dateSelected: Date
#Binding var mnthNm: Date
#Binding var pageCurrent: Date
var calendar = FSCalendar()
var today: Date{
return Date()
}
func makeUIView(context: Context) -> FSCalendar {
calendar.dataSource = context.coordinator
calendar.delegate = context.coordinator
calendar.appearance.headerMinimumDissolvedAlpha = 0
return calendar
}
func updateUIView(_ uiView: FSCalendar, context: Context) {
uiView.setCurrentPage(pageCurrent, animated: true) // --->> update calendar view when click on left or right button
}
func makeCoordinator() -> CustomCalendar.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, FSCalendarDelegate, FSCalendarDataSource {
var parent: CustomCalendar
init(_ parent: CustomCalendar) {
self.parent = parent
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
parent.dateSelected = date
}
func calendarCurrentPageDidChange(_ calendar: FSCalendar) {
parent.pageCurrent = calendar.currentPage
parent.mnthNm = calendar.currentPage
}
}
}
Output:

how to use #published and #observed with FSCalendar?

How to get for example selectedDate from FSCalendar?
struct CalendarController: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CalendarController>)
-> calendars {
let calendarViewController = calendars()
return calendarViewController
}
func updateUIViewController(_ uiViewController: calendars, context:
UIViewControllerRepresentableContext<CalendarController>) {
}
func updateUIViewController(_ uiViewController: CalendarController, context:
UIViewControllerRepresentableContext<CalendarController>) {
}
class Coordinator: NSObject {
var parent: CalendarController
init(_ calendarViewController: CalendarController) {
self.parent = calendarViewController
}
}
}
class calendars: UIViewController, FSCalendarDelegate, ObservableObject {
var calendar = FSCalendar()
#Published var selectedData : Date?
override func viewDidLoad() {
super.viewDidLoad()
calendar.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
calendar.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height:
view.frame.size.width)
view.addSubview(calendar)
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition:
FSCalendarMonthPosition) {
print("didSelect date: \(date)")
selectedData = date
}
}
struct CalendarView: View {
var body: some View{
CalendarController().padding().frame(alignment: .top)
}
}
struct Calendar_Previews: PreviewProvider {
static var previews: some View {
CalendarView()
}
}
Later in another swift view I try to get the date, but it is never changed.
I use the
#ObservedObject var calendarData = calendars()
//...
CalendarView().scaledToFit()
Text("\(self.calendarData.selectedData ?? Date())")
the following works.
As I am new to iOS, swift, I could not figure out the trick with delegate.
Coordinator class shall implement it to make it work.
'''
import SwiftUI
import UIKit
import FSCalendar
import Combine
struct CalendarController: UIViewControllerRepresentable {
#Binding var selectedDate : Date?
func makeCoordinator() -> CalendarController.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> calendars {
let calendar = calendars()
calendar.calendar.delegate = context.coordinator
return calendar
}
func updateUIViewController(_ uiViewController: calendars, context: Context) {
}
}
class calendars: UIViewController {
var calendar = FSCalendar()
var selectedDate : Date?
override func viewDidLoad() {
super.viewDidLoad()
calendar.delegate = self // delegate
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
calendar.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height:
view.frame.size.width)
view.addSubview(calendar)
}
}
extension CalendarController {
class Coordinator: NSObject, FSCalendarDelegate {
var parent: CalendarController
init(_ parent: CalendarController) {
self.parent = parent
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at
monthPosition: FSCalendarMonthPosition) {
print(date)
parent.selectedDate = date
}
}
}
'''

FSCalendar integration in SwiftUI

I'm currently trying to add a calendar interface in my app where when you click on a day at the bottom it will show details about events on that day. Currently I am using FSCalendar as my calendar.
I realised that this library is for UIKit and I would have to wrap it with representable protocol to get it working in SwiftUI.
I've been watching youtube and looking up guides on integrating UIKit in SwiftUI to help me do this. This is what I have currently:
import SwiftUI
import FSCalendar
CalendarModule.swift:
class CalendarModule: UIViewController, FSCalendarDelegate {
var calendar = FSCalendar()
override func viewDidLoad() {
super.viewDidLoad()
calendar.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
initCalendar()
view.addSubview(calendar)
}
private func initCalendar() {
calendar.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.width)
calendar.appearance.todayColor = UIColor.systemGreen
calendar.appearance.selectionColor = UIColor.systemBlue
}
}
struct CalendarModuleViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<CalendarModuleViewController>) -> UIViewController {
let viewController = CalendarModule()
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CalendarModuleViewController>) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
final class Coordinator: NSObject, FSCalendarDelegate {
private var parent: CalendarModuleViewController
init (_ parent: CalendarModuleViewController) {
self.parent = parent
}
}
}
struct CalendarModuleView: View {
var body: some View {
CalendarModuleViewController()
}
}
CalendarView.swift - Displaying calendar in top half and the details in bottom half:
import SwiftUI
struct CalendarView: View {
var body: some View {
NavigationView {
VStack{
VStack {
Spacer()
CalendarModuleView()
Spacer()
}
VStack {
Spacer()
Text("Details")
Spacer()
}
}.navigationBarTitle(Text("Calendar"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: CreateEventView().environmentObject(Event())) {
Image(systemName: "plus").imageScale(.large)
}.buttonStyle(DefaultButtonStyle())
)
}
}
}
struct CalendarView_Previews: PreviewProvider {
static var previews: some View {
CalendarView()
}
}
ContentView - Just displaying my CalendarView:
import SwiftUI
struct ContentView: View {
var body: some View {
CalendarView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The calendar looks like this so far:
The calendar module itself works but I got stuck in writing the Coordinator to handle the delegates. Specifically the didSelectDate one..
When I start typing didSelectDate in the Coordinator class and look through the suggestions I only get
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
<#code#>
}
Am I wrapping the view controller wrong?
Or should I be making my own UIView and UIViewController for FSCalendar then create UIViewRepresentable and UIViewControllerRepresentable to use in SwiftUI?
This is an old question, but still.
I think the problem is that you assign CalendarModule as a delegate and not the Coordinator
you need to write the following code:
func makeUIViewController(context: UIViewControllerRepresentableContext<CalendarModuleViewController>) -> UIViewController {
let viewController = CalendarModule()
viewController.calendar.delegate = context.coordinator // ->> Add this
return viewController
}
After that, you should be able to implement delegate methods in the Coordinator