0

I have a UIViewRepresentable of a third-party library component FSCalendar. However, I need this to conform to type UIView... Is there a way to do this? Any help is appreciated :)

struct CalendarViewRepresentable: UIViewRepresentable {

    typealias UIViewType = FSCalendar
    var calendar = FSCalendar()

    @Binding var selectedDate: Date

    var calendarHeight: NSLayoutConstraint?


    func updateUIView(_ uiView: FSCalendar, context: Context) { }


    func makeUIView(context: Context) -> FSCalendar {

        calendar.delegate = context.coordinator
        calendar.dataSource = context.coordinator

        calendar.translatesAutoresizingMaskIntoConstraints = false

        calendar.setContentHuggingPriority(.required, for: .vertical)
        calendar.setContentHuggingPriority(.required, for: .horizontal)

        NSLayoutConstraint.activate([
            calendar.topAnchor.constraint(equalTo: context.coordinator.topAnchor)
        ])

        return calendar
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, FSCalendarDelegate, FSCalendarDataSource {

        var parent: CalendarViewRepresentable

        init(_ parent: CalendarViewRepresentable) {
    
            self.parent = parent     
    
        }


        func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {

            parent.selectedDate = date
    
        }

        func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {

            parent.calendarHeight?.constant = bounds.height
            parent.calendar.layoutIfNeeded()
    
        }
 
    }

}

struct HomeView: View {

    @State private var selectedDate: Date = Date()

    var body: some View {
  
        VStack {
            CalendarViewRepresentable(selectedDate: self.$selectedDate)
        }

    }
}
devOP1
  • 295
  • 3
  • 13
  • 1
    Why don't you just use `FSCalendar` directly? As `UIViewRepresentable` is for using a `UIView` in `SwiftUI,` if you need to use it in `UIKit` then you don't need the `UIViewRepresentable` – Andrew Nov 18 '22 at 16:55
  • @Andrew I am using it in SwiftUI... I have added the code where it is called into the SwiftUI view above. Sorry for not being clear – devOP1 Nov 18 '22 at 16:59
  • Why do you think you need a view to inherit from `UIView` if you use it in SwiftUI? – Dávid Pásztor Nov 18 '22 at 17:01
  • @DávidPásztor so I can use NSLayoutConstraints and adjust the height of the view dynamically... Please check this question: https://stackoverflow.com/questions/74491934/how-to-use-nslayoutconstraints-in-uiviewrepresentable – devOP1 Nov 18 '22 at 17:02
  • @devOP1 if you want to apply constraints, you need to do that __inside__ the `UIViewRepresentable`, in the `makeUIView` method. Autolayout constraints don't exist in SwiftUI, so you cannot use them on Views. Nor can you use a `UIView` in SwiftUI. That's what `UIViewRepresentable` is for, to wrap a `UIView` (and any UIKit features, such as auto layout constraints) in a container that SwiftUI can handle. – Dávid Pásztor Nov 18 '22 at 17:05
  • @DávidPásztor I have tried adding the constraints directly in the UIViewRepresentable already but without any luck... If you could please provide me with an example I would very much appreciate it. I have added what I previously tried above – devOP1 Nov 18 '22 at 17:07

2 Answers2

0

We usually wrap a UIKit view (UIView) to be UIViewRepresentable whenever we want to use this view in a SwiftUI context. SwiftUI works with a view element called View.

On your case the UIKit view is FSCalendar, it's originally a UIView and wrapping it to be a UIViewRepresentable is helping us using it in a SwiftUI context as a SwiftUI View - therefore there is no reason to convert or make UIViewRepresentable to a UIView.

L A
  • 966
  • 11
  • 25
  • The reason I want it to inherit `UIView` is so I can set NSLayoutConstraints – devOP1 Nov 18 '22 at 17:01
  • I see, but in SwiftUI we don't use Auto-layout therefore we don't need NSLayoutConstraints. You'll use your new UIViewRepresentable just like any other SwiftUI View. SwiftUI will do the hard work for you, when u'll use this View in HStack, VStack...etc. – L A Nov 18 '22 at 17:03
  • Yes, but I would like the height of the UIViewRepresentable to adjust dynamically. Please check this question: https://stackoverflow.com/questions/74491934/how-to-use-nslayoutconstraints-in-uiviewrepresentable – devOP1 Nov 18 '22 at 17:04
  • Answer to this will be on the other question you created, since it doesn't directly relate to this one. – L A Nov 18 '22 at 17:09
0

Your implementation of UIViewRepresentable with a Coordinator isn't quite right, give this a try:

import SwiftUI
import FSCalendar

struct CalendarViewRepresentable: UIViewRepresentable {
    
    @Binding var selectedDate: Date
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    func makeUIView(context: Context) -> FSCalendar {
        return context.coordinator.calendar
    }
    
    func updateUIView(_ uiView: FSCalendar, context: Context) {
        uiView.select(selectedDate)
        context.coordinator.didSelectDate = { date in
            selectedDate = date
        }
    }
    
    class Coordinator: NSObject, FSCalendarDelegate, FSCalendarDataSource {
        
        lazy var calendar: FSCalendar = {
            let calendar = FSCalendar()
            calendar.delegate = self
            calendar.dataSource = self
            return calendar
        }()
        
        var didSelectDate: ((Date) -> ())?
        
        func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
            didSelectDate?(date)
        }
    }
}

struct FSCalendarTest: View {
    @State private var selectedDate = Date()
    
    var body: some View {
        NavigationStack {
            CalendarViewRepresentable(selectedDate: self.$selectedDate)
                .navigationTitle("\(selectedDate, format: .dateTime)")
                .toolbar {
                    Button("Now") {
                        selectedDate = Date()
                    }
                }
        }
    }
}

Screenshot

malhal
  • 26,330
  • 7
  • 115
  • 133