我可以在 SwiftUI 应用程序中使用 View Controller (CalendarKit) 吗?

Posted

技术标签:

【中文标题】我可以在 SwiftUI 应用程序中使用 View Controller (CalendarKit) 吗?【英文标题】:Can I use View Controller (CalendarKit) in SwiftUI application? 【发布时间】:2020-10-19 17:54:38 【问题描述】:

我想在我的项目中使用 CalendarKit github here

它是使用 UIKit 编写的,但我的项目使用的是 SwiftUI。我可以在 SwiftUI 中使用CustomCalendarExampleController 吗? (也许通过UIViewControllerRepresentable 或其他方式?) CustomCalendarExampleController -

class CustomCalendarExampleController: DayViewController, DatePickerControllerDelegate 
  
  var data = [["Breakfast at Tiffany's",
               "New York, 5th avenue"],
              
              ["Workout",
               "Tufteparken"],
              
              ["Meeting with Alex",
               "Home",
               "Oslo, Tjuvholmen"],
              
              ["Beach Volleyball",
               "Ipanema Beach",
               "Rio De Janeiro"],
              
              ["WWDC",
               "Moscone West Convention Center",
               "747 Howard St"],
              
              ["Google I/O",
               "Shoreline Amphitheatre",
               "One Amphitheatre Parkway"],
              
              ["✈️️ to Svalbard ❄️️❄️️❄️️❤️️",
               "Oslo Gardermoen"],
              
              ["???????? Developing CalendarKit",
               "???? Worldwide"],
              
              ["Software Development Lecture",
               "Mikpoli MB310",
               "Craig Federighi"],
              
  ]
  
  var generatedEvents = [EventDescriptor]()
  var alreadyGeneratedSet = Set<Date>()
  
  var colors = [UIColor.blue,
                UIColor.yellow,
                UIColor.green,
                UIColor.red]

  lazy var customCalendar: Calendar = 
    let customNSCalendar = NSCalendar(identifier: NSCalendar.Identifier.gregorian)!
    customNSCalendar.timeZone = TimeZone(abbreviation: "CEST")!
    let calendar = customNSCalendar as Calendar
    return calendar
  ()
  
  override func loadView() 
    calendar = customCalendar
    dayView = DayView(calendar: calendar)
    view = dayView
  
  
  override func viewDidLoad() 
    super.viewDidLoad()
    title = "CalendarKit Demo"
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Change Date",
                                                       style: .plain,
                                                       target: self,
                                                       action: #selector(presentDatePicker))
    navigationController?.navigationBar.isTranslucent = false
    dayView.autoScrollToFirstEvent = true
    reloadData()
  
  
  @objc func presentDatePicker() 
    let picker = DatePickerController()
    //    let calendar = dayView.calendar
    //    picker.calendar = calendar
    //    picker.date = dayView.state!.selectedDate
    picker.datePicker.timeZone = TimeZone(secondsFromGMT: 0)!
    picker.delegate = self
    let navC = UINavigationController(rootViewController: picker)
    navigationController?.present(navC, animated: true, completion: nil)
  
  
  func datePicker(controller: DatePickerController, didSelect date: Date?) 
    if let date = date 
      var utcCalendar = Calendar(identifier: .gregorian)
      utcCalendar.timeZone = TimeZone(secondsFromGMT: 0)!
      
      let offsetDate = dateOnly(date: date, calendar: dayView.calendar)
      
      print(offsetDate)
      dayView.state?.move(to: offsetDate)
    
    controller.dismiss(animated: true, completion: nil)
  
  
  func dateOnly(date: Date, calendar: Calendar) -> Date 
    let yearComponent = calendar.component(.year, from: date)
    let monthComponent = calendar.component(.month, from: date)
    let dayComponent = calendar.component(.day, from: date)
    let zone = calendar.timeZone
    
    let newComponents = DateComponents(timeZone: zone,
                                       year: yearComponent,
                                       month: monthComponent,
                                       day: dayComponent)
    let returnValue = calendar.date(from: newComponents)
    
    //    let returnValue = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: date)
    
    
    return returnValue!
  
  
  // MARK: EventDataSource
  
  override func eventsForDate(_ date: Date) -> [EventDescriptor] 
    if !alreadyGeneratedSet.contains(date) 
      alreadyGeneratedSet.insert(date)
      generatedEvents.append(contentsOf: generateEventsForDate(date))
    
    return generatedEvents
  
   private func generateEventsForDate(_ date: Date) -> [EventDescriptor] 
     var workingDate = date.add(TimeChunk.dateComponents(hours: Int(arc4random_uniform(10) + 5)))
    var events = [Event]()
    
    for i in 0...4 
      let event = Event()
      let duration = Int(arc4random_uniform(160) + 60)
      let datePeriod = TimePeriod(beginning: workingDate,
                                  chunk: TimeChunk.dateComponents(minutes: duration))
      
      event.startDate = datePeriod.beginning!
      event.endDate = datePeriod.end!
      
      var info = data[Int(arc4random_uniform(UInt32(data.count)))]
      
      let timezone = dayView.calendar.timeZone
      print(timezone)
      info.append(datePeriod.beginning!.format(with: "dd.MM.YYYY", timeZone: timezone))
      info.append("\(datePeriod.beginning!.format(with: "HH:mm", timeZone: timezone)) - \(datePeriod.end!.format(with: "HH:mm", timeZone: timezone))")
      event.text = info.reduce("", $0 + $1 + "\n")
      event.color = colors[Int(arc4random_uniform(UInt32(colors.count)))]
      event.isAllDay = Int(arc4random_uniform(2)) % 2 == 0
      
      // Event styles are updated independently from CalendarStyle
      // hence the need to specify exact colors in case of Dark style
      if #available(ios 12.0, *) 
        if traitCollection.userInterfaceStyle == .dark 
          event.textColor = textColorForEventInDarkTheme(baseColor: event.color)
          event.backgroundColor = event.color.withAlphaComponent(0.6)
        
      
      
      events.append(event)
      
      let nextOffset = Int(arc4random_uniform(250) + 40)
      workingDate = workingDate.add(TimeChunk.dateComponents(minutes: nextOffset))
      event.userInfo = String(i)
    

    print("Events for \(date)")
    return events
  
  
  private func textColorForEventInDarkTheme(baseColor: UIColor) -> UIColor 
    var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
    baseColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
    return UIColor(hue: h, saturation: s * 0.3, brightness: b, alpha: a)
  
  
  // MARK: DayViewDelegate
  
  private var createdEvent: EventDescriptor?
  
  override func dayViewDidSelectEventView(_ eventView: EventView) 
    guard let descriptor = eventView.descriptor as? Event else 
      return
    
    print("Event has been selected: \(descriptor) \(String(describing: descriptor.userInfo))")
  
  
  override func dayViewDidLongPressEventView(_ eventView: EventView) 
    guard let descriptor = eventView.descriptor as? Event else 
      return
    
    endEventEditing()
    print("Event has been longPressed: \(descriptor) \(String(describing: descriptor.userInfo))")
    beginEditing(event: descriptor, animated: true)
    print(Date())
  
  
  override func dayView(dayView: DayView, didTapTimelineAt date: Date) 
    endEventEditing()
    print("Did Tap at date: \(date)")
  
  
  override func dayViewDidBeginDragging(dayView: DayView) 
    print("DayView did begin dragging")
  
  
  override func dayView(dayView: DayView, willMoveTo date: Date) 
    print("DayView = \(dayView) will move to: \(date)")
  
  
  override func dayView(dayView: DayView, didMoveTo date: Date) 
    print("DayView = \(dayView) did move to: \(date)")
  
  
  override func dayView(dayView: DayView, didLongPressTimelineAt date: Date) 
    print("Did long press timeline at date \(date)")
    // Cancel editing current event and start creating a new one
    endEventEditing()
    let event = generateEventNearDate(date)
    print("Creating a new event")
    create(event: event, animated: true)
    createdEvent = event
  
  
  private func generateEventNearDate(_ date: Date) -> EventDescriptor 
    let duration = Int(arc4random_uniform(160) + 60)
    let startDate = date.subtract(TimeChunk.dateComponents(minutes: Int(CGFloat(duration) / 2)))
    let event = Event()
    let datePeriod = TimePeriod(beginning: startDate,
                                chunk: TimeChunk.dateComponents(minutes: duration))
    event.startDate = datePeriod.beginning!
    event.endDate = datePeriod.end!
    
    var info = data[Int(arc4random_uniform(UInt32(data.count)))]
    let timezone = dayView.calendar.timeZone
    info.append(datePeriod.beginning!.format(with: "dd.MM.YYYY", timeZone: timezone))
    info.append("\(datePeriod.beginning!.format(with: "HH:mm", timeZone: timezone)) - \(datePeriod.end!.format(with: "HH:mm", timeZone: timezone))")
    event.text = info.reduce("", $0 + $1 + "\n")
    event.color = colors[Int(arc4random_uniform(UInt32(colors.count)))]
    event.editedEvent = event
    
    // Event styles are updated independently from CalendarStyle
    // hence the need to specify exact colors in case of Dark style
    if #available(iOS 12.0, *) 
      if traitCollection.userInterfaceStyle == .dark 
        event.textColor = textColorForEventInDarkTheme(baseColor: event.color)
        event.backgroundColor = event.color.withAlphaComponent(0.6)
      
    
    return event
  
  
  override func dayView(dayView: DayView, didUpdate event: EventDescriptor) 
    print("did finish editing \(event)")
    print("new startDate: \(event.startDate) new endDate: \(event.endDate)")
    
    if let _ = event.editedEvent 
      event.commitEditing()
    
    
    if let createdEvent = createdEvent 
      createdEvent.editedEvent = nil
      generatedEvents.append(createdEvent)
      self.createdEvent = nil
      endEventEditing()
    
    
    reloadData()
  

DayViewController -

#if os(iOS)
import UIKit
import DateToolsSwift

open class DayViewController: UIViewController, EventDataSource, DayViewDelegate 
  public lazy var dayView: DayView = DayView()
  public var dataSource: EventDataSource? 
    get 
      return dayView.dataSource
    
    set(value) 
      dayView.dataSource = value
    
  

  public var delegate: DayViewDelegate? 
    get 
      return dayView.delegate
    
    set(value) 
      dayView.delegate = value
    
  

  public var calendar = Calendar.autoupdatingCurrent 
    didSet 
      dayView.calendar = calendar
    
  

  open override func loadView() 
    view = dayView
  

  override open func viewDidLoad() 
    super.viewDidLoad()
    edgesForExtendedLayout = []
    view.tintColor = SystemColors.systemRed
    dataSource = self
    delegate = self
    dayView.reloadData()

    let sizeClass = traitCollection.horizontalSizeClass
    configureDayViewLayoutForHorizontalSizeClass(sizeClass)
  

  open override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)
    dayView.scrollToFirstEventIfNeeded()
  

  open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) 
    super.willTransition(to: newCollection, with: coordinator)
    configureDayViewLayoutForHorizontalSizeClass(newCollection.horizontalSizeClass)
  

  open func configureDayViewLayoutForHorizontalSizeClass(_ sizeClass: UIUserInterfaceSizeClass) 
    dayView.transitionToHorizontalSizeClass(sizeClass)
  
  
  // MARK: - CalendarKit API
  
  open func move(to date: Date) 
    dayView.move(to: date)
  

  open func reloadData() 
    dayView.reloadData()
  

  open func updateStyle(_ newStyle: CalendarStyle) 
    dayView.updateStyle(newStyle)
  

  open func eventsForDate(_ date: Date) -> [EventDescriptor] 
    return [Event]()
  

  // MARK: - DayViewDelegate

  open func dayViewDidSelectEventView(_ eventView: EventView) 
  

  open func dayViewDidLongPressEventView(_ eventView: EventView) 
  

  open func dayView(dayView: DayView, didTapTimelineAt date: Date) 
  
  
  open func dayViewDidBeginDragging(dayView: DayView)  
  

  open func dayView(dayView: DayView, willMoveTo date: Date) 
  

  open func dayView(dayView: DayView, didMoveTo date: Date) 
  

  open func dayView(dayView: DayView, didLongPressTimelineAt date: Date) 
  

  open func dayView(dayView: DayView, didUpdate event: EventDescriptor) 
  
  
  // MARK: - Editing
  
  open func create(event: EventDescriptor, animated: Bool = false) 
    dayView.create(event: event, animated: animated)
  

  open func beginEditing(event: EventDescriptor, animated: Bool = false) 
    dayView.beginEditing(event: event, animated: animated)
  
  
  open func endEventEditing() 
    dayView.endEventEditing()
  

#endif

DatePickerControllerDelegate 没什么好玩的。

感谢您的回复!

【问题讨论】:

是的,你可以把它包装在 UIViewControllerRepresentable 中 【参考方案1】:

大声笑我不知道为什么它以前不起作用,但如果你正在寻找这样的东西

struct CustomController: UIViewControllerRepresentable 
    func updateUIViewController(_ uiViewController: UIViewController, context: Context)
    
    
    func makeUIViewController(context: Context) -> UIViewController 
        let dayViewController = CustomCalendarExampleController()
        return dayViewController
    

【讨论】:

这是目前使用 CalendarKit 的正确方法。在 UIKit 对应部分中编写代码,然后桥接到 SwiftUI

以上是关于我可以在 SwiftUI 应用程序中使用 View Controller (CalendarKit) 吗?的主要内容,如果未能解决你的问题,请参考以下文章

手动更新 SwiftUI View

SwiftUI - 解释 View 中数据刷新时 EnvironmentObject 与 ObservableObject 行为的差异

如何在 SwiftUI 中创建一个获取 View 并返回自定义结果的函数?

如何创建一个带有可选辅助 View 参数的 SwiftUI 视图?

SwiftUI 官方教程

SwiftUI button 与sheet的使用