feat(mobile): add native inline calendar for ios

This commit is contained in:
charlie
2025-05-31 19:16:28 +08:00
parent 5234eb22fb
commit 44cbbd0c8c

View File

@@ -5,80 +5,131 @@
// Created by Charlie on 2025/5/29.
//
import Foundation
import Capacitor
import Foundation
class DatePickerDialogViewController: UIViewController {
private let datePicker = UIDatePicker()
private let dialogView = UIView()
var onDateSelected: ((Date?) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
setupImplView()
}
@objc private func confirmDate() {
onDateSelected?(datePicker.date)
dismiss(animated: true, completion: nil)
}
@objc private func dismissDialog() {
onDateSelected?(nil)
dismiss(animated: true, completion: nil)
}
func setupImplView() {
datePicker.datePickerMode = .date
datePicker.preferredDatePickerStyle = .inline
datePicker.addTarget(
self, action: #selector(confirmDate), for: .valueChanged)
// Create hosting view controller
let view = self.view!
view.backgroundColor = .black.withAlphaComponent(0.4)
dialogView.backgroundColor = .white
dialogView.layer.cornerRadius = 10
dialogView.clipsToBounds = true
view.addSubview(dialogView)
dialogView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
dialogView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
dialogView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// Add sub views
dialogView.addSubview(datePicker)
// Add date selector and toolbar to the view
datePicker.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
datePicker.topAnchor.constraint(equalTo: dialogView.topAnchor),
datePicker.bottomAnchor.constraint(equalTo: dialogView.bottomAnchor, constant: -8),
datePicker.leadingAnchor.constraint(equalTo: dialogView.leadingAnchor, constant: 16),
datePicker.trailingAnchor.constraint(equalTo: dialogView.trailingAnchor, constant: -16),
])
datePicker.setContentHuggingPriority(.required, for: .horizontal)
datePicker.setContentHuggingPriority(.required, for: .vertical)
datePicker.setContentCompressionResistancePriority(
.required, for: .horizontal)
datePicker.setContentCompressionResistancePriority(
.required, for: .vertical)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: view)
if !dialogView.frame.contains(location) {
dismiss(animated: true, completion: nil)
}
}
super.touchesBegan(touches, with: event)
}
}
@objc(UILocalPlugin)
public class UILocalPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "UILocalPlugin"
public let jsName = "UILocal"
private var call: CAPPluginCall?
private var selectedDate: Date?
private var datepickerViewController: UIViewController?
private var datepickerDialogView: UIView?
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "showDatePicker", returnType: CAPPluginReturnPromise)
]
@objc func showDatePicker(_ call: CAPPluginCall) {
self.call = call
DispatchQueue.main.async { [weak self] in
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
datePicker.preferredDatePickerStyle = .wheels
datePicker.addTarget(self, action: #selector(self?.dateChanged(_:)), for: .valueChanged)
// Create hosting view controller
let viewController = UIViewController()
viewController.view.backgroundColor = .white
// Create the top toolbar (including cancel and confirm buttons)
let toolbar = UIToolbar()
toolbar.translatesAutoresizingMaskIntoConstraints = false
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self?.cancelTapped))
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self?.confirmTapped))
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolbar.setItems([cancelButton, flexibleSpace, doneButton], animated: false)
toolbar.sizeToFit()
// Add date selector and toolbar to the view
datePicker.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(datePicker)
viewController.view.addSubview(toolbar)
// Set layout constraints
NSLayoutConstraint.activate([
toolbar.topAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.topAnchor),
toolbar.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor),
datePicker.topAnchor.constraint(equalTo: toolbar.bottomAnchor, constant: 10),
datePicker.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor),
datePicker.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor),
datePicker.bottomAnchor.constraint(lessThanOrEqualTo: viewController.view.safeAreaLayoutGuide.bottomAnchor, constant: -10)
])
// Set bottom sheet style (compatible with iOS 13+)
viewController.modalPresentationStyle = .formSheet
viewController.isModalInPresentation = true
let viewController = DatePickerDialogViewController()
// Set view controller presentation
viewController.modalPresentationStyle = .overFullScreen
viewController.modalTransitionStyle = .crossDissolve
viewController.isModalInPresentation = true //
viewController.onDateSelected = self?.dateChanged
// Present View Controller
guard let presentingViewController = self?.bridge?.viewController else {
call.reject("Unable to present date picker")
return
}
presentingViewController.present(viewController, animated: true, completion: nil)
presentingViewController.present(
viewController, animated: true, completion: nil)
}
}
@objc private func dateChanged(_ datePicker: UIDatePicker) {
self.selectedDate = datePicker.date
self.call?.keepAlive = true // Keep calling until confirmed or canceled
private func dateChanged(_ date: Date?) {
self.selectedDate = date
self.call?.keepAlive = true // Keep calling until confirmed or canceled
onDateSelected()
}
@objc private func confirmTapped() {
private func onDateSelected() {
if let date = self.selectedDate {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
@@ -92,16 +143,11 @@ public class UILocalPlugin: CAPPlugin, CAPBridgedPlugin {
let result: PluginCallResultData = ["value": dateString]
self.call?.resolve(result)
}
self.bridge?.viewController?.dismiss(animated: true, completion: nil)
}
@objc private func cancelTapped() {
self.call?.reject("Date selection cancelled")
self.bridge?.viewController?.dismiss(animated: true, completion: nil)
}
override public func load () {
override public func load() {
print("🔅 UILocalPlugin loaded")
}
}