From 0d163bf559771baffa4ce12532e0623423ea2256 Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Wed, 24 Sep 2025 21:52:47 +0530 Subject: [PATCH 1/8] changes --- ios/PlacePickerViewController.swift | 362 +++++++++++++++++++--------- 1 file changed, 243 insertions(+), 119 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index f4beb49..fa92dd8 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -5,10 +5,9 @@ // Created by b0iq on 17/06/2022. // - -import UIKit -import MapKit import ExpoModulesCore +import MapKit +import UIKit class PlacePickerViewController: UIViewController { // MARK: - Variables @@ -24,12 +23,12 @@ class PlacePickerViewController: UIViewController { } private var firstMapLoad: Bool = true private var lastLocation: CLPlacemark? - private var mapMoveDebounceTimer:Timer? + private var mapMoveDebounceTimer: Timer? private let geocoder = CLGeocoder() private let locationManager = CLLocationManager() - + // MARK: - Inits - init(_ options: PlacePickerOptions,_ promise: Promise) { + init(_ options: PlacePickerOptions, _ promise: Promise) { self.promise = promise self.options = options super.init(nibName: nil, bundle: nil) @@ -37,170 +36,241 @@ class PlacePickerViewController: UIViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - UI Views private lazy var mapPinShadow: UIView = { let shadowView = UIView() - shadowView.backgroundColor = UIColor(options.color).withAlphaComponent(0.5) + shadowView.backgroundColor = UIColor(options.color).withAlphaComponent(0.3) shadowView.translatesAutoresizingMaskIntoConstraints = false - shadowView.layer.cornerRadius = 2.5 + shadowView.layer.cornerRadius = 4 + shadowView.layer.shadowColor = UIColor.black.cgColor + shadowView.layer.shadowOpacity = 0.2 + shadowView.layer.shadowOffset = CGSize(width: 0, height: 2) + shadowView.layer.shadowRadius = 6 return shadowView }() + private lazy var pinImage: UIView = { let pinImage: UIImageView if #available(iOS 13.0, *) { - pinImage = UIImageView(image: UIImage(systemName: "mappin")) + pinImage = UIImageView(image: UIImage(systemName: "mappin.circle.fill")) } else { pinImage = UIImageView(image: UIImage(named: "mappin")) } - pinImage.contentMode = .center + pinImage.contentMode = .scaleAspectFit pinImage.tintColor = UIColor(options.contrastColor) - pinImage.frame = CGRect(x: 0, y: 0, width: 40, height: 40) + pinImage.frame = CGRect(x: 0, y: 0, width: 30, height: 30) return pinImage }() + private lazy var pinLoading: UIActivityIndicatorView = { - let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) + let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) loader.color = UIColor(options.contrastColor) loader.hidesWhenStopped = true return loader }() + private lazy var mapPinContentView: UIView = { - let pinContainer = UIView(frame: CGRect(x: 5, y: 4, width: 40, height: 40)) + let pinContainer = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) pinContainer.layer.cornerRadius = 20 pinContainer.backgroundColor = UIColor(options.color) + pinContainer.layer.shadowColor = UIColor.black.cgColor + pinContainer.layer.shadowOpacity = 0.2 + pinContainer.layer.shadowOffset = CGSize(width: 0, height: 2) + pinContainer.layer.shadowRadius = 4 pinContainer.addSubview(pinImage) pinContainer.addSubview(pinLoading) + + // Center the pin image and loading indicator + pinImage.center = CGPoint( + x: pinContainer.bounds.width / 2, y: pinContainer.bounds.height / 2) + pinLoading.center = CGPoint( + x: pinContainer.bounds.width / 2, y: pinContainer.bounds.height / 2) + return pinContainer }() + private lazy var mapPin: UIView = { - let heightWidth = 10 - let path = CGMutablePath() - path.move(to: CGPoint(x:20, y: 43)) - path.addLine(to: CGPoint(x:(heightWidth/2) + 20, y: (heightWidth/2) + 43)) - path.addLine(to: CGPoint(x:heightWidth + 20, y:43)) - path.addLine(to: CGPoint(x:20, y:43)) - let shape = CAShapeLayer() - shape.path = path - shape.fillColor = UIColor(options.color).cgColor let pinView = UIView() - pinView.layer.insertSublayer(shape, at: 0) + + // Create a custom pin shape using bezier path + let pinTailPath = UIBezierPath() + pinTailPath.move(to: CGPoint(x: 20, y: 40)) + pinTailPath.addLine(to: CGPoint(x: 16, y: 46)) + pinTailPath.addLine(to: CGPoint(x: 24, y: 46)) + pinTailPath.close() + + let tailShape = CAShapeLayer() + tailShape.path = pinTailPath.cgPath + tailShape.fillColor = UIColor(options.color).cgColor + + pinView.layer.insertSublayer(tailShape, at: 0) pinView.addSubview(mapPinContentView) pinView.translatesAutoresizingMaskIntoConstraints = false + return pinView }() + private lazy var mapView: MKMapView = { let map = MKMapView() - map.showsUserLocation = true - map.showsBuildings = true - map.showsTraffic = false - map.showsCompass = true - map.showsScale = true - map.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: options.initialCoordinates.latitude, longitude: options.initialCoordinates.longitude), latitudinalMeters: 1000, longitudinalMeters: 1000) + map.showsUserLocation = true + map.showsBuildings = true + map.showsTraffic = false + map.showsCompass = true + map.showsScale = true + if #available(iOS 13.0, *) { + map.overrideUserInterfaceStyle = .light + } + map.region = MKCoordinateRegion( + center: CLLocationCoordinate2D( + latitude: options.initialCoordinates.latitude, + longitude: options.initialCoordinates.longitude), + latitudinalMeters: 1000, + longitudinalMeters: 1000) map.translatesAutoresizingMaskIntoConstraints = false return map }() - + private lazy var searchResultContainer: DropDown = { let view = DropDown() view.translatesAutoresizingMaskIntoConstraints = false view.isHidden = true view.isOpaque = true + view.layer.cornerRadius = 12 + view.layer.shadowColor = UIColor.black.cgColor + view.layer.shadowOpacity = 0.15 + view.layer.shadowOffset = CGSize(width: 0, height: 2) + view.layer.shadowRadius = 8 + view.clipsToBounds = false return view }() - + // MARK: - UI setup methods private func setupViews() { + // Set view background color + self.view.backgroundColor = .white + // MARK: - 1 Setup map view self.view.addSubview(mapView) NSLayoutConstraint.activate([ mapView.widthAnchor.constraint(equalTo: self.view.widthAnchor), mapView.heightAnchor.constraint(equalTo: self.view.heightAnchor), - // mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), - // mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) + self.view.addSubview(mapPinShadow) NSLayoutConstraint.activate([ mapPinShadow.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), - mapPinShadow.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor), - mapPinShadow.widthAnchor.constraint(equalToConstant: 5), - mapPinShadow.heightAnchor.constraint(equalToConstant: 5) + mapPinShadow.centerYAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.centerYAnchor), + mapPinShadow.widthAnchor.constraint(equalToConstant: 8), + mapPinShadow.heightAnchor.constraint(equalToConstant: 8), ]) - + mapPin.setAnchorPoint(CGPoint(x: 0.5, y: 1)) self.view.addSubview(mapPin) NSLayoutConstraint.activate([ mapPin.centerXAnchor.constraint(equalTo: self.mapView.centerXAnchor), - mapPin.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor, constant: 25), + mapPin.bottomAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.centerYAnchor, constant: 20), mapPin.widthAnchor.constraint(equalToConstant: 50), - mapPin.heightAnchor.constraint(equalToConstant: 50) + mapPin.heightAnchor.constraint(equalToConstant: 50), ]) + self.view.addSubview(searchResultContainer) NSLayoutConstraint.activate([ - searchResultContainer.widthAnchor.constraint(equalTo: self.view.widthAnchor), - searchResultContainer.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), - searchResultContainer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) + searchResultContainer.widthAnchor.constraint( + equalTo: self.view.widthAnchor, constant: -32), + searchResultContainer.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + searchResultContainer.topAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 56), + searchResultContainer.bottomAnchor.constraint( + lessThanOrEqualTo: self.view.bottomAnchor, constant: -100), ]) - + searchResultContainer.delegate = self - // MARK: - 2 Setup naivgation bar + + // MARK: - 2 Setup navigation bar setupNavigationBar() } + private func setupNavigationBar() { // MARK: - 1 Make cancel button - let customCancelButton = UIButton() + let customCancelButton = UIButton(type: .system) customCancelButton.tintColor = UIColor(options.color) + customCancelButton.layer.cornerRadius = 8 + customCancelButton.backgroundColor = UIColor(options.contrastColor).withAlphaComponent(0.1) + customCancelButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12) + if #available(iOS 13.0, *) { - let cancelImage = UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + let cancelImage = UIImage( + systemName: "xmark.circle.fill", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold)) customCancelButton.setImage(cancelImage, for: .normal) } else { customCancelButton.setTitle("Cancel", for: .normal) } customCancelButton.addTarget(self, action: #selector(closePicker), for: .touchUpInside) - + // MARK: - 2 Make done button - let customDoneButton = UIButton() - customDoneButton.tintColor = UIColor(options.color) + let customDoneButton = UIButton(type: .system) + customDoneButton.tintColor = .white + customDoneButton.backgroundColor = UIColor(options.color) + customDoneButton.layer.cornerRadius = 8 + customDoneButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12) + if #available(iOS 13.0, *) { - let checkImage = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + let checkImage = UIImage( + systemName: "checkmark", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold)) customDoneButton.setImage(checkImage, for: .normal) + customDoneButton.setTitle(" Done", for: .normal) } else { customDoneButton.setTitle("Done", for: .normal) } customDoneButton.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) - + // MARK: - 3 Make user location button - let customUserLocationButton = UIButton() + let customUserLocationButton = UIButton(type: .system) customUserLocationButton.tintColor = UIColor(options.color) + customUserLocationButton.backgroundColor = UIColor(options.contrastColor) + .withAlphaComponent(0.1) + customUserLocationButton.layer.cornerRadius = 8 + customUserLocationButton.contentEdgeInsets = UIEdgeInsets( + top: 8, left: 12, bottom: 8, right: 12) + if #available(iOS 13.0, *) { - let checkImage = UIImage(systemName: "location", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) - customUserLocationButton.setImage(checkImage, for: .normal) + let locationImage = UIImage( + systemName: "location.circle.fill", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold)) + customUserLocationButton.setImage(locationImage, for: .normal) } else { customUserLocationButton.setTitle("location", for: .normal) } - customUserLocationButton.addTarget(self, action: #selector(pickUserLocation), for: .touchUpInside) - - if #available(iOS 15.0, *) { - customDoneButton.configuration = .borderedTinted() - customCancelButton.configuration = .borderedProminent() - customUserLocationButton.configuration = .bordered() - } - + customUserLocationButton.addTarget( + self, action: #selector(pickUserLocation), for: .touchUpInside) + let customCancelButtonItem = UIBarButtonItem(customView: customCancelButton) let customDoneButtonItem = UIBarButtonItem(customView: customDoneButton) let customUserLocationButtonItem = UIBarButtonItem(customView: customUserLocationButton) - - if (options.enableSearch) { + + if options.enableSearch { if #available(iOS 13.0, *) { searchController.automaticallyShowsCancelButton = true searchController.searchBar.searchTextField.clearButtonMode = .whileEditing searchController.searchBar.showsCancelButton = false + searchController.searchBar.searchTextField.backgroundColor = UIColor.systemGray6 + searchController.searchBar.tintColor = UIColor(options.color) } else { searchController.searchBar.setValue("OK", forKey: "cancelButtonText") } searchController.searchBar.placeholder = options.searchPlaceholder searchController.searchBar.enablesReturnKeyAutomatically = true searchController.searchBar.returnKeyType = .search - + + // Customize search bar appearance + searchController.searchBar.layer.cornerRadius = 10 + searchController.searchBar.clipsToBounds = true + searchController.searchResultsUpdater = self searchController.searchBar.delegate = self searchController.obscuresBackgroundDuringPresentation = false @@ -209,71 +279,96 @@ class PlacePickerViewController: UIViewController { definesPresentationContext = true navigationItem.searchController = searchController } - - if (options.enableLargeTitle) { + + if options.enableLargeTitle { self.navigationItem.largeTitleDisplayMode = .automatic self.navigationController?.navigationBar.prefersLargeTitles = true } var rightItems = [customDoneButtonItem] - if (options.enableUserLocation) { + if options.enableUserLocation { rightItems.append(customUserLocationButtonItem) } self.navigationItem.leftBarButtonItem = customCancelButtonItem self.navigationItem.rightBarButtonItems = rightItems } - + // MARK: - UIViewController Lifecycle override func viewWillAppear(_ animated: Bool) { if #available(iOS 13, *) { let appearance = UINavigationBarAppearance() appearance.configureWithDefaultBackground() + appearance.backgroundColor = .white + appearance.shadowColor = .clear + + // Customize the navigation bar text + appearance.titleTextAttributes = [ + .foregroundColor: UIColor.darkText, + .font: UIFont.systemFont(ofSize: 17, weight: .semibold), + ] + + appearance.largeTitleTextAttributes = [ + .foregroundColor: UIColor.darkText, + .font: UIFont.systemFont(ofSize: 28, weight: .bold), + ] + navigationController?.navigationBar.standardAppearance = appearance navigationController?.navigationBar.scrollEdgeAppearance = appearance navigationController?.navigationBar.isTranslucent = true } } + override func viewDidLoad() { super.viewDidLoad() self.title = options.title - if (options.enableUserLocation) { + if options.enableUserLocation { locationManager.delegate = self locationManager.requestWhenInUseAuthorization() completer.delegate = self } setupViews() } + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - mapView.centerCoordinate = CLLocationCoordinate2D(latitude: options.initialCoordinates.latitude, longitude: options.initialCoordinates.longitude) + mapView.centerCoordinate = CLLocationCoordinate2D( + latitude: options.initialCoordinates.latitude, + longitude: options.initialCoordinates.longitude) mapView.delegate = self } + override func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() - if (!firstMapLoad) { + if !firstMapLoad { UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() } mapView.setCenter(mapView.centerCoordinate, animated: true) } else { firstMapLoad = false } } + override func viewDidDisappear(_ animated: Bool) { - if (promise != nil && options.rejectOnCancel) { + if promise != nil && options.rejectOnCancel { promise?.reject("dismissed", "Modal closed by user") } } - + // MARK: - Navigation bar buttons methods @objc private func pickUserLocation() { locationManager.requestLocation() } + @objc private func closePicker() { - if (options.rejectOnCancel) { - if (promise != nil) { - promise?.reject("cancel", "User cancel the operation and `rejectOnCancel` is enabled") + if options.rejectOnCancel { + if promise != nil { + promise?.reject( + "cancel", "User cancel the operation and `rejectOnCancel` is enabled") } } else { let result = PlacePickerResult( - coordinate: .init(wrappedValue: PlacePickerCoordinate(latitude: .init(wrappedValue: mapView.centerCoordinate.latitude), longitude: .init(wrappedValue: mapView.centerCoordinate.longitude))), + coordinate: .init( + wrappedValue: PlacePickerCoordinate( + latitude: .init(wrappedValue: mapView.centerCoordinate.latitude), + longitude: .init(wrappedValue: mapView.centerCoordinate.longitude))), address: .init(wrappedValue: PlacePickerAddress(with: self.lastLocation)), didCancel: .init(wrappedValue: true)) promise?.resolve(result) @@ -283,9 +378,13 @@ class PlacePickerViewController: UIViewController { self.dismiss(animated: true) } } + @objc private func finalizePicker() { let result = PlacePickerResult( - coordinate: .init(wrappedValue: PlacePickerCoordinate(latitude: .init(wrappedValue: mapView.centerCoordinate.latitude), longitude: .init(wrappedValue: mapView.centerCoordinate.longitude))), + coordinate: .init( + wrappedValue: PlacePickerCoordinate( + latitude: .init(wrappedValue: mapView.centerCoordinate.latitude), + longitude: .init(wrappedValue: mapView.centerCoordinate.longitude))), address: .init(wrappedValue: PlacePickerAddress(with: self.lastLocation)), didCancel: .init(wrappedValue: false)) promise?.resolve(result) @@ -294,114 +393,137 @@ class PlacePickerViewController: UIViewController { self.dismiss(animated: true) } } - + // MARK: - Private methods private func setLoading(_ state: Bool) { pinImage.isHidden = state - if (state) { + if state { pinLoading.startAnimating() } else { pinLoading.stopAnimating() } } + private func mapWillMove() { startPinAnimation() } + private func mapDidMove() { - if (options.enableGeocoding) { + if options.enableGeocoding { setLoading(true) - geocoder.reverseGeocodeLocation(CLLocation(latitude: mapView.centerCoordinate.latitude, longitude: mapView.centerCoordinate.longitude), preferredLocale: Locale(identifier: options.locale)) { location, error in - if let _ = error { + geocoder.reverseGeocodeLocation( + CLLocation( + latitude: mapView.centerCoordinate.latitude, + longitude: mapView.centerCoordinate.longitude), + preferredLocale: Locale(identifier: options.locale) + ) { location, error in + if error != nil { self.setLoading(false) self.endPinAnimation() self.lastLocation = nil - self.navigationItem.searchController?.searchBar.placeholder = self.options.searchPlaceholder + self.navigationItem.searchController?.searchBar.placeholder = + self.options.searchPlaceholder return } self.lastLocation = location?.first if let name = location?.first?.name { self.navigationItem.searchController?.searchBar.placeholder = name } else { - self.navigationItem.searchController?.searchBar.placeholder = self.options.searchPlaceholder + self.navigationItem.searchController?.searchBar.placeholder = + self.options.searchPlaceholder } self.setLoading(false) self.endPinAnimation() - } } else { self.endPinAnimation() } } + private func startPinAnimation() { - UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut], animations: { - self.mapPin.transform = CGAffineTransform.identity.scaledBy(x: 1.3, y: 1.3).translatedBy(x: 0, y: -10) - }) + UIView.animate( + withDuration: 0.3, delay: 0, options: [.curveEaseOut], + animations: { + self.mapPin.transform = CGAffineTransform.identity.scaledBy(x: 1.2, y: 1.2) + .translatedBy(x: 0, y: -15) + + // Add a subtle spring effect + self.mapPinShadow.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) + self.mapPinShadow.alpha = 0.7 + }) } + private func endPinAnimation(_ comp: ((Bool) -> Void)? = nil) { - let rotationAmount: CGFloat = 0.5 - UIView.animateKeyframes(withDuration: 1.8, - delay: 0, - animations: { - UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/6) { - self.mapPin.transform = CGAffineTransform.identity - } - UIView.addKeyframe(withRelativeStartTime: 1 / 6, relativeDuration: 1/6) { - self.mapPin.transform = CGAffineTransform.identity.rotated(by: -rotationAmount / 2) - } - UIView.addKeyframe(withRelativeStartTime: 2 / 6, relativeDuration: 1/6) { - self.mapPin.transform = CGAffineTransform.identity.rotated(by: rotationAmount / 3) - } - UIView.addKeyframe(withRelativeStartTime: 3 / 6, relativeDuration: 1/6) { - self.mapPin.transform = CGAffineTransform.identity.rotated(by: -rotationAmount / 4) - } - UIView.addKeyframe(withRelativeStartTime: 4 / 6, relativeDuration: 1/6) { - self.mapPin.transform = CGAffineTransform.identity.rotated(by: rotationAmount / 5) - } - UIView.addKeyframe(withRelativeStartTime: 5 / 6, relativeDuration: 1/6) { - self.mapPin.transform = CGAffineTransform.identity - } - }, completion: comp) + UIView.animateKeyframes( + withDuration: 0.8, + delay: 0, + options: .calculationModeCubic, + animations: { + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { + self.mapPin.transform = CGAffineTransform.identity + self.mapPinShadow.transform = CGAffineTransform.identity + self.mapPinShadow.alpha = 1.0 + } + + // Add a subtle bounce effect + UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.2) { + self.mapPin.transform = CGAffineTransform.identity.scaledBy(x: 1.1, y: 0.9) + } + + UIView.addKeyframe(withRelativeStartTime: 0.7, relativeDuration: 0.3) { + self.mapPin.transform = CGAffineTransform.identity + } + }, completion: comp) } } extension PlacePickerViewController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { mapDidMove() - } + func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { mapWillMove() } - } + extension PlacePickerViewController: UISearchBarDelegate { func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { self.completerResults.removeAll() return true } + func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { // print("DID SEARCH") } } + extension PlacePickerViewController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { if let coordinate = locations.first?.coordinate { mapView.setCenter(coordinate, animated: true) } } + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print(error) } } + extension PlacePickerViewController: DropDownButtonDelegate { func didSelect(_ index: Int) { let selectedResult = completerResults[index] - if let title = selectedResult.attrTitle?.string, let subTitle = selectedResult.attrSubtitle?.string { + if let title = selectedResult.attrTitle?.string, + let subTitle = selectedResult.attrSubtitle?.string + { let request = MKLocalSearch.Request() - request.naturalLanguageQuery = subTitle.contains(title) ? subTitle : title + ", " + subTitle + request.naturalLanguageQuery = + subTitle.contains(title) ? subTitle : title + ", " + subTitle let search = MKLocalSearch(request: request) search.start { [weak self] (result, error) in - guard error == nil, let coords = result?.mapItems.first?.placemark.coordinate else {return} + guard error == nil, let coords = result?.mapItems.first?.placemark.coordinate else { + return + } self?.mapView.setCenter(coords, animated: true) self?.searchController.searchBar.text = "" self?.searchController.isActive = false @@ -409,6 +531,7 @@ extension PlacePickerViewController: DropDownButtonDelegate { } } } + extension PlacePickerViewController: MKLocalSearchCompleterDelegate { func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { completerResults = completer.results.map { r in @@ -419,6 +542,7 @@ extension PlacePickerViewController: MKLocalSearchCompleterDelegate { } } } + extension PlacePickerViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { if let searchText = searchController.searchBar.text, !searchText.isEmpty { From b940411ae310b319f6f5bfc5a561d06930a163fb Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Wed, 24 Sep 2025 22:09:32 +0530 Subject: [PATCH 2/8] changes --- ios/PlacePickerViewController.swift | 396 ++++++++++++---------------- 1 file changed, 174 insertions(+), 222 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index fa92dd8..543c5ea 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -13,8 +13,6 @@ class PlacePickerViewController: UIViewController { // MARK: - Variables private var promise: Promise? private let options: PlacePickerOptions - private let searchController = UISearchController() - private let completer = MKLocalSearchCompleter() private var completerResults: [CustomSearchCompletion] = [] { didSet { searchResultContainer.dataSource = completerResults @@ -26,6 +24,7 @@ class PlacePickerViewController: UIViewController { private var mapMoveDebounceTimer: Timer? private let geocoder = CLGeocoder() private let locationManager = CLLocationManager() + private let completer = MKLocalSearchCompleter() // MARK: - Inits init(_ options: PlacePickerOptions, _ promise: Promise) { @@ -33,11 +32,34 @@ class PlacePickerViewController: UIViewController { self.options = options super.init(nibName: nil, bundle: nil) } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - UI Views + private lazy var customSearchBar: UISearchBar = { + let searchBar = UISearchBar() + searchBar.placeholder = options.searchPlaceholder + searchBar.barTintColor = .white + searchBar.backgroundImage = UIImage() + searchBar.layer.cornerRadius = 12 + searchBar.layer.masksToBounds = true + searchBar.searchBarStyle = .minimal + searchBar.tintColor = UIColor(options.color) + searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default) + searchBar.translatesAutoresizingMaskIntoConstraints = false + + // Add shadow + searchBar.layer.shadowColor = UIColor.black.cgColor + searchBar.layer.shadowOffset = CGSize(width: 0, height: 2) + searchBar.layer.shadowRadius = 4 + searchBar.layer.shadowOpacity = 0.1 + searchBar.delegate = self + + return searchBar + }() + private lazy var mapPinShadow: UIView = { let shadowView = UIView() shadowView.backgroundColor = UIColor(options.color).withAlphaComponent(0.3) @@ -50,19 +72,6 @@ class PlacePickerViewController: UIViewController { return shadowView }() - private lazy var pinImage: UIView = { - let pinImage: UIImageView - if #available(iOS 13.0, *) { - pinImage = UIImageView(image: UIImage(systemName: "mappin.circle.fill")) - } else { - pinImage = UIImageView(image: UIImage(named: "mappin")) - } - pinImage.contentMode = .scaleAspectFit - pinImage.tintColor = UIColor(options.contrastColor) - pinImage.frame = CGRect(x: 0, y: 0, width: 30, height: 30) - return pinImage - }() - private lazy var pinLoading: UIActivityIndicatorView = { let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) loader.color = UIColor(options.contrastColor) @@ -70,47 +79,6 @@ class PlacePickerViewController: UIViewController { return loader }() - private lazy var mapPinContentView: UIView = { - let pinContainer = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) - pinContainer.layer.cornerRadius = 20 - pinContainer.backgroundColor = UIColor(options.color) - pinContainer.layer.shadowColor = UIColor.black.cgColor - pinContainer.layer.shadowOpacity = 0.2 - pinContainer.layer.shadowOffset = CGSize(width: 0, height: 2) - pinContainer.layer.shadowRadius = 4 - pinContainer.addSubview(pinImage) - pinContainer.addSubview(pinLoading) - - // Center the pin image and loading indicator - pinImage.center = CGPoint( - x: pinContainer.bounds.width / 2, y: pinContainer.bounds.height / 2) - pinLoading.center = CGPoint( - x: pinContainer.bounds.width / 2, y: pinContainer.bounds.height / 2) - - return pinContainer - }() - - private lazy var mapPin: UIView = { - let pinView = UIView() - - // Create a custom pin shape using bezier path - let pinTailPath = UIBezierPath() - pinTailPath.move(to: CGPoint(x: 20, y: 40)) - pinTailPath.addLine(to: CGPoint(x: 16, y: 46)) - pinTailPath.addLine(to: CGPoint(x: 24, y: 46)) - pinTailPath.close() - - let tailShape = CAShapeLayer() - tailShape.path = pinTailPath.cgPath - tailShape.fillColor = UIColor(options.color).cgColor - - pinView.layer.insertSublayer(tailShape, at: 0) - pinView.addSubview(mapPinContentView) - pinView.translatesAutoresizingMaskIntoConstraints = false - - return pinView - }() - private lazy var mapView: MKMapView = { let map = MKMapView() map.showsUserLocation = true @@ -145,186 +113,178 @@ class PlacePickerViewController: UIViewController { return view }() + private lazy var closeButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Cancel", for: .normal) + if #available(iOS 13.0, *) { + let cancelImage = UIImage(systemName: "xmark.circle.fill") + button.setImage(cancelImage, for: .normal) + button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8) + } + button.setTitleColor(UIColor(options.color), for: .normal) + button.backgroundColor = UIColor(options.contrastColor).withAlphaComponent(0.1) + button.layer.cornerRadius = 12 + button.layer.shadowColor = UIColor.black.cgColor + button.layer.shadowOpacity = 0.1 + button.layer.shadowOffset = CGSize(width: 0, height: 1) + button.layer.shadowRadius = 3 + button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(closePicker), for: .touchUpInside) + return button + }() + + private lazy var doneButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Done", for: .normal) + if #available(iOS 13.0, *) { + let doneImage = UIImage(systemName: "checkmark") + button.setImage(doneImage, for: .normal) + button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8) + } + button.setTitleColor(UIColor.white, for: .normal) + button.backgroundColor = UIColor(options.color) + button.layer.cornerRadius = 12 + button.layer.shadowColor = UIColor.black.cgColor + button.layer.shadowOpacity = 0.2 + button.layer.shadowOffset = CGSize(width: 0, height: 2) + button.layer.shadowRadius = 4 + button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) + return button + }() + + private lazy var locationButton: UIButton = { + let button = UIButton(type: .system) + if #available(iOS 13.0, *) { + let locationImage = UIImage(systemName: "location.circle.fill") + button.setImage(locationImage, for: .normal) + } else { + button.setTitle("My Location", for: .normal) + } + button.setTitleColor(UIColor(options.color), for: .normal) + button.tintColor = UIColor(options.color) + button.backgroundColor = UIColor.white + button.layer.cornerRadius = 12 + button.layer.shadowColor = UIColor.black.cgColor + button.layer.shadowOpacity = 0.2 + button.layer.shadowOffset = CGSize(width: 0, height: 2) + button.layer.shadowRadius = 4 + button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(pickUserLocation), for: .touchUpInside) + return button + }() + // MARK: - UI setup methods private func setupViews() { // Set view background color self.view.backgroundColor = .white - // MARK: - 1 Setup map view + // MARK: - 1 Setup map view and default Apple pin self.view.addSubview(mapView) NSLayoutConstraint.activate([ - mapView.widthAnchor.constraint(equalTo: self.view.widthAnchor), - mapView.heightAnchor.constraint(equalTo: self.view.heightAnchor), + mapView.topAnchor.constraint(equalTo: self.view.topAnchor), + mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), + mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) - self.view.addSubview(mapPinShadow) + // Add title label + let titleLabel = UILabel() + titleLabel.text = options.title + titleLabel.font = UIFont.boldSystemFont(ofSize: 20) + titleLabel.textAlignment = .center + titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(titleLabel) + NSLayoutConstraint.activate([ - mapPinShadow.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), - mapPinShadow.centerYAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.centerYAnchor), - mapPinShadow.widthAnchor.constraint(equalToConstant: 8), - mapPinShadow.heightAnchor.constraint(equalToConstant: 8), + titleLabel.topAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 16), + titleLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16), + titleLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16), ]) - mapPin.setAnchorPoint(CGPoint(x: 0.5, y: 1)) - self.view.addSubview(mapPin) + // Add custom search bar + self.view.addSubview(customSearchBar) NSLayoutConstraint.activate([ - mapPin.centerXAnchor.constraint(equalTo: self.mapView.centerXAnchor), - mapPin.bottomAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.centerYAnchor, constant: 20), - mapPin.widthAnchor.constraint(equalToConstant: 50), - mapPin.heightAnchor.constraint(equalToConstant: 50), + customSearchBar.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), + customSearchBar.leadingAnchor.constraint( + equalTo: self.view.leadingAnchor, constant: 16), + customSearchBar.trailingAnchor.constraint( + equalTo: self.view.trailingAnchor, constant: -16), + customSearchBar.heightAnchor.constraint(equalToConstant: 44), ]) self.view.addSubview(searchResultContainer) NSLayoutConstraint.activate([ - searchResultContainer.widthAnchor.constraint( - equalTo: self.view.widthAnchor, constant: -32), - searchResultContainer.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), searchResultContainer.topAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 56), - searchResultContainer.bottomAnchor.constraint( - lessThanOrEqualTo: self.view.bottomAnchor, constant: -100), + equalTo: customSearchBar.bottomAnchor, constant: 8), + searchResultContainer.leadingAnchor.constraint( + equalTo: self.view.leadingAnchor, constant: 16), + searchResultContainer.trailingAnchor.constraint( + equalTo: self.view.trailingAnchor, constant: -16), + searchResultContainer.heightAnchor.constraint(lessThanOrEqualToConstant: 220), ]) searchResultContainer.delegate = self - // MARK: - 2 Setup navigation bar - setupNavigationBar() - } - - private func setupNavigationBar() { - // MARK: - 1 Make cancel button - let customCancelButton = UIButton(type: .system) - customCancelButton.tintColor = UIColor(options.color) - customCancelButton.layer.cornerRadius = 8 - customCancelButton.backgroundColor = UIColor(options.contrastColor).withAlphaComponent(0.1) - customCancelButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12) + // Add buttons + self.view.addSubview(closeButton) + self.view.addSubview(doneButton) - if #available(iOS 13.0, *) { - let cancelImage = UIImage( - systemName: "xmark.circle.fill", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold)) - customCancelButton.setImage(cancelImage, for: .normal) - } else { - customCancelButton.setTitle("Cancel", for: .normal) - } - customCancelButton.addTarget(self, action: #selector(closePicker), for: .touchUpInside) + NSLayoutConstraint.activate([ + closeButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16), + closeButton.bottomAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -16), - // MARK: - 2 Make done button - let customDoneButton = UIButton(type: .system) - customDoneButton.tintColor = .white - customDoneButton.backgroundColor = UIColor(options.color) - customDoneButton.layer.cornerRadius = 8 - customDoneButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12) + doneButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16), + doneButton.bottomAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -16), + ]) - if #available(iOS 13.0, *) { - let checkImage = UIImage( - systemName: "checkmark", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold)) - customDoneButton.setImage(checkImage, for: .normal) - customDoneButton.setTitle(" Done", for: .normal) - } else { - customDoneButton.setTitle("Done", for: .normal) + if options.enableUserLocation { + self.view.addSubview(locationButton) + NSLayoutConstraint.activate([ + locationButton.trailingAnchor.constraint( + equalTo: self.view.trailingAnchor, constant: -16), + locationButton.topAnchor.constraint( + equalTo: searchResultContainer.bottomAnchor, constant: 16), + locationButton.widthAnchor.constraint(equalToConstant: 44), + locationButton.heightAnchor.constraint(equalToConstant: 44), + ]) } - customDoneButton.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) - - // MARK: - 3 Make user location button - let customUserLocationButton = UIButton(type: .system) - customUserLocationButton.tintColor = UIColor(options.color) - customUserLocationButton.backgroundColor = UIColor(options.contrastColor) - .withAlphaComponent(0.1) - customUserLocationButton.layer.cornerRadius = 8 - customUserLocationButton.contentEdgeInsets = UIEdgeInsets( - top: 8, left: 12, bottom: 8, right: 12) - if #available(iOS 13.0, *) { - let locationImage = UIImage( - systemName: "location.circle.fill", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold)) - customUserLocationButton.setImage(locationImage, for: .normal) - } else { - customUserLocationButton.setTitle("location", for: .normal) - } - customUserLocationButton.addTarget( - self, action: #selector(pickUserLocation), for: .touchUpInside) - - let customCancelButtonItem = UIBarButtonItem(customView: customCancelButton) - let customDoneButtonItem = UIBarButtonItem(customView: customDoneButton) - let customUserLocationButtonItem = UIBarButtonItem(customView: customUserLocationButton) - - if options.enableSearch { - if #available(iOS 13.0, *) { - searchController.automaticallyShowsCancelButton = true - searchController.searchBar.searchTextField.clearButtonMode = .whileEditing - searchController.searchBar.showsCancelButton = false - searchController.searchBar.searchTextField.backgroundColor = UIColor.systemGray6 - searchController.searchBar.tintColor = UIColor(options.color) - } else { - searchController.searchBar.setValue("OK", forKey: "cancelButtonText") - } - searchController.searchBar.placeholder = options.searchPlaceholder - searchController.searchBar.enablesReturnKeyAutomatically = true - searchController.searchBar.returnKeyType = .search - - // Customize search bar appearance - searchController.searchBar.layer.cornerRadius = 10 - searchController.searchBar.clipsToBounds = true - - searchController.searchResultsUpdater = self - searchController.searchBar.delegate = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = false - navigationItem.hidesSearchBarWhenScrolling = false - definesPresentationContext = true - navigationItem.searchController = searchController - } + // Shadow under the pin + self.view.addSubview(mapPinShadow) + NSLayoutConstraint.activate([ + mapPinShadow.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + mapPinShadow.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), + mapPinShadow.widthAnchor.constraint(equalToConstant: 8), + mapPinShadow.heightAnchor.constraint(equalToConstant: 8), + ]) - if options.enableLargeTitle { - self.navigationItem.largeTitleDisplayMode = .automatic - self.navigationController?.navigationBar.prefersLargeTitles = true - } - var rightItems = [customDoneButtonItem] - if options.enableUserLocation { - rightItems.append(customUserLocationButtonItem) - } - self.navigationItem.leftBarButtonItem = customCancelButtonItem - self.navigationItem.rightBarButtonItems = rightItems + // Add loading indicator for pin + pinLoading.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(pinLoading) + NSLayoutConstraint.activate([ + pinLoading.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + pinLoading.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20), + pinLoading.widthAnchor.constraint(equalToConstant: 30), + pinLoading.heightAnchor.constraint(equalToConstant: 30), + ]) } // MARK: - UIViewController Lifecycle - override func viewWillAppear(_ animated: Bool) { - if #available(iOS 13, *) { - let appearance = UINavigationBarAppearance() - appearance.configureWithDefaultBackground() - appearance.backgroundColor = .white - appearance.shadowColor = .clear - - // Customize the navigation bar text - appearance.titleTextAttributes = [ - .foregroundColor: UIColor.darkText, - .font: UIFont.systemFont(ofSize: 17, weight: .semibold), - ] - - appearance.largeTitleTextAttributes = [ - .foregroundColor: UIColor.darkText, - .font: UIFont.systemFont(ofSize: 28, weight: .bold), - ] - - navigationController?.navigationBar.standardAppearance = appearance - navigationController?.navigationBar.scrollEdgeAppearance = appearance - navigationController?.navigationBar.isTranslucent = true - } - } - override func viewDidLoad() { super.viewDidLoad() - self.title = options.title if options.enableUserLocation { locationManager.delegate = self locationManager.requestWhenInUseAuthorization() completer.delegate = self } + navigationController?.setNavigationBarHidden(true, animated: false) setupViews() } @@ -352,7 +312,7 @@ class PlacePickerViewController: UIViewController { } } - // MARK: - Navigation bar buttons methods + // MARK: - Button action methods @objc private func pickUserLocation() { locationManager.requestLocation() } @@ -396,7 +356,6 @@ class PlacePickerViewController: UIViewController { // MARK: - Private methods private func setLoading(_ state: Bool) { - pinImage.isHidden = state if state { pinLoading.startAnimating() } else { @@ -421,16 +380,14 @@ class PlacePickerViewController: UIViewController { self.setLoading(false) self.endPinAnimation() self.lastLocation = nil - self.navigationItem.searchController?.searchBar.placeholder = - self.options.searchPlaceholder + self.customSearchBar.placeholder = self.options.searchPlaceholder return } self.lastLocation = location?.first if let name = location?.first?.name { - self.navigationItem.searchController?.searchBar.placeholder = name + self.customSearchBar.placeholder = name } else { - self.navigationItem.searchController?.searchBar.placeholder = - self.options.searchPlaceholder + self.customSearchBar.placeholder = self.options.searchPlaceholder } self.setLoading(false) self.endPinAnimation() @@ -444,10 +401,7 @@ class PlacePickerViewController: UIViewController { UIView.animate( withDuration: 0.3, delay: 0, options: [.curveEaseOut], animations: { - self.mapPin.transform = CGAffineTransform.identity.scaledBy(x: 1.2, y: 1.2) - .translatedBy(x: 0, y: -15) - - // Add a subtle spring effect + // Use shadow animation to indicate pin movement self.mapPinShadow.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) self.mapPinShadow.alpha = 0.7 }) @@ -460,19 +414,9 @@ class PlacePickerViewController: UIViewController { options: .calculationModeCubic, animations: { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { - self.mapPin.transform = CGAffineTransform.identity self.mapPinShadow.transform = CGAffineTransform.identity self.mapPinShadow.alpha = 1.0 } - - // Add a subtle bounce effect - UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.2) { - self.mapPin.transform = CGAffineTransform.identity.scaledBy(x: 1.1, y: 0.9) - } - - UIView.addKeyframe(withRelativeStartTime: 0.7, relativeDuration: 0.3) { - self.mapPin.transform = CGAffineTransform.identity - } }, completion: comp) } } @@ -488,13 +432,21 @@ extension PlacePickerViewController: MKMapViewDelegate { } extension PlacePickerViewController: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + if !searchText.isEmpty { + completer.queryFragment = searchText + } else { + completerResults.removeAll() + } + } + func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { self.completerResults.removeAll() return true } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - // print("DID SEARCH") + searchBar.resignFirstResponder() } } @@ -525,8 +477,8 @@ extension PlacePickerViewController: DropDownButtonDelegate { return } self?.mapView.setCenter(coords, animated: true) - self?.searchController.searchBar.text = "" - self?.searchController.isActive = false + self?.customSearchBar.text = "" + self?.customSearchBar.resignFirstResponder() } } } From 05cd1f16c774489f6b0658f77f1f6204b0d8276f Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Wed, 24 Sep 2025 23:50:37 +0530 Subject: [PATCH 3/8] changes --- ios/PlacePickerViewController.swift | 502 +++++++++++++++------------- 1 file changed, 277 insertions(+), 225 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index 543c5ea..16891ec 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -13,6 +13,8 @@ class PlacePickerViewController: UIViewController { // MARK: - Variables private var promise: Promise? private let options: PlacePickerOptions + private let searchController = UISearchController() + private let completer = MKLocalSearchCompleter() private var completerResults: [CustomSearchCompletion] = [] { didSet { searchResultContainer.dataSource = completerResults @@ -24,7 +26,6 @@ class PlacePickerViewController: UIViewController { private var mapMoveDebounceTimer: Timer? private let geocoder = CLGeocoder() private let locationManager = CLLocationManager() - private let completer = MKLocalSearchCompleter() // MARK: - Inits init(_ options: PlacePickerOptions, _ promise: Promise) { @@ -32,53 +33,60 @@ class PlacePickerViewController: UIViewController { self.options = options super.init(nibName: nil, bundle: nil) } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - UI Views - private lazy var customSearchBar: UISearchBar = { - let searchBar = UISearchBar() - searchBar.placeholder = options.searchPlaceholder - searchBar.barTintColor = .white - searchBar.backgroundImage = UIImage() - searchBar.layer.cornerRadius = 12 - searchBar.layer.masksToBounds = true - searchBar.searchBarStyle = .minimal - searchBar.tintColor = UIColor(options.color) - searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default) - searchBar.translatesAutoresizingMaskIntoConstraints = false - - // Add shadow - searchBar.layer.shadowColor = UIColor.black.cgColor - searchBar.layer.shadowOffset = CGSize(width: 0, height: 2) - searchBar.layer.shadowRadius = 4 - searchBar.layer.shadowOpacity = 0.1 - searchBar.delegate = self - - return searchBar - }() - private lazy var mapPinShadow: UIView = { let shadowView = UIView() - shadowView.backgroundColor = UIColor(options.color).withAlphaComponent(0.3) + shadowView.backgroundColor = UIColor(options.color).withAlphaComponent(0.5) shadowView.translatesAutoresizingMaskIntoConstraints = false - shadowView.layer.cornerRadius = 4 - shadowView.layer.shadowColor = UIColor.black.cgColor - shadowView.layer.shadowOpacity = 0.2 - shadowView.layer.shadowOffset = CGSize(width: 0, height: 2) - shadowView.layer.shadowRadius = 6 + shadowView.layer.cornerRadius = 2.5 return shadowView }() - + private lazy var pinImage: UIView = { + let pinImage: UIImageView + if #available(iOS 13.0, *) { + pinImage = UIImageView(image: UIImage(systemName: "mappin")) + } else { + pinImage = UIImageView(image: UIImage(named: "mappin")) + } + pinImage.contentMode = .center + pinImage.tintColor = UIColor(options.contrastColor) + pinImage.frame = CGRect(x: 0, y: 0, width: 40, height: 40) + return pinImage + }() private lazy var pinLoading: UIActivityIndicatorView = { - let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) + let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) loader.color = UIColor(options.contrastColor) loader.hidesWhenStopped = true return loader }() - + private lazy var mapPinContentView: UIView = { + let pinContainer = UIView(frame: CGRect(x: 5, y: 4, width: 40, height: 40)) + pinContainer.layer.cornerRadius = 20 + pinContainer.backgroundColor = UIColor(options.color) + pinContainer.addSubview(pinImage) + pinContainer.addSubview(pinLoading) + return pinContainer + }() + private lazy var mapPin: UIView = { + let heightWidth = 10 + let path = CGMutablePath() + path.move(to: CGPoint(x: 20, y: 43)) + path.addLine(to: CGPoint(x: (heightWidth / 2) + 20, y: (heightWidth / 2) + 43)) + path.addLine(to: CGPoint(x: heightWidth + 20, y: 43)) + path.addLine(to: CGPoint(x: 20, y: 43)) + let shape = CAShapeLayer() + shape.path = path + shape.fillColor = UIColor(options.color).cgColor + let pinView = UIView() + pinView.layer.insertSublayer(shape, at: 0) + pinView.addSubview(mapPinContentView) + pinView.translatesAutoresizingMaskIntoConstraints = false + return pinView + }() private lazy var mapView: MKMapView = { let map = MKMapView() map.showsUserLocation = true @@ -86,14 +94,10 @@ class PlacePickerViewController: UIViewController { map.showsTraffic = false map.showsCompass = true map.showsScale = true - if #available(iOS 13.0, *) { - map.overrideUserInterfaceStyle = .light - } map.region = MKCoordinateRegion( center: CLLocationCoordinate2D( latitude: options.initialCoordinates.latitude, - longitude: options.initialCoordinates.longitude), - latitudinalMeters: 1000, + longitude: options.initialCoordinates.longitude), latitudinalMeters: 1000, longitudinalMeters: 1000) map.translatesAutoresizingMaskIntoConstraints = false return map @@ -104,190 +108,238 @@ class PlacePickerViewController: UIViewController { view.translatesAutoresizingMaskIntoConstraints = false view.isHidden = true view.isOpaque = true - view.layer.cornerRadius = 12 - view.layer.shadowColor = UIColor.black.cgColor - view.layer.shadowOpacity = 0.15 - view.layer.shadowOffset = CGSize(width: 0, height: 2) - view.layer.shadowRadius = 8 - view.clipsToBounds = false return view }() - private lazy var closeButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Cancel", for: .normal) - if #available(iOS 13.0, *) { - let cancelImage = UIImage(systemName: "xmark.circle.fill") - button.setImage(cancelImage, for: .normal) - button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8) - } - button.setTitleColor(UIColor(options.color), for: .normal) - button.backgroundColor = UIColor(options.contrastColor).withAlphaComponent(0.1) - button.layer.cornerRadius = 12 - button.layer.shadowColor = UIColor.black.cgColor - button.layer.shadowOpacity = 0.1 - button.layer.shadowOffset = CGSize(width: 0, height: 1) - button.layer.shadowRadius = 3 - button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(closePicker), for: .touchUpInside) - return button - }() - - private lazy var doneButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Done", for: .normal) - if #available(iOS 13.0, *) { - let doneImage = UIImage(systemName: "checkmark") - button.setImage(doneImage, for: .normal) - button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8) - } - button.setTitleColor(UIColor.white, for: .normal) - button.backgroundColor = UIColor(options.color) - button.layer.cornerRadius = 12 - button.layer.shadowColor = UIColor.black.cgColor - button.layer.shadowOpacity = 0.2 - button.layer.shadowOffset = CGSize(width: 0, height: 2) - button.layer.shadowRadius = 4 - button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) - return button - }() - - private lazy var locationButton: UIButton = { - let button = UIButton(type: .system) - if #available(iOS 13.0, *) { - let locationImage = UIImage(systemName: "location.circle.fill") - button.setImage(locationImage, for: .normal) - } else { - button.setTitle("My Location", for: .normal) - } - button.setTitleColor(UIColor(options.color), for: .normal) - button.tintColor = UIColor(options.color) - button.backgroundColor = UIColor.white - button.layer.cornerRadius = 12 - button.layer.shadowColor = UIColor.black.cgColor - button.layer.shadowOpacity = 0.2 - button.layer.shadowOffset = CGSize(width: 0, height: 2) - button.layer.shadowRadius = 4 - button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12) - button.translatesAutoresizingMaskIntoConstraints = false - button.addTarget(self, action: #selector(pickUserLocation), for: .touchUpInside) - return button - }() - // MARK: - UI setup methods private func setupViews() { - // Set view background color - self.view.backgroundColor = .white - - // MARK: - 1 Setup map view and default Apple pin + // MARK: - 1 Setup map view self.view.addSubview(mapView) NSLayoutConstraint.activate([ - mapView.topAnchor.constraint(equalTo: self.view.topAnchor), - mapView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), - mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), - mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + mapView.widthAnchor.constraint(equalTo: self.view.widthAnchor), + mapView.heightAnchor.constraint(equalTo: self.view.heightAnchor), ]) - - // Add title label - let titleLabel = UILabel() - titleLabel.text = options.title - titleLabel.font = UIFont.boldSystemFont(ofSize: 20) - titleLabel.textAlignment = .center - titleLabel.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(titleLabel) - + self.view.addSubview(mapPinShadow) NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 16), - titleLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16), - titleLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16), + mapPinShadow.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + mapPinShadow.centerYAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.centerYAnchor), + mapPinShadow.widthAnchor.constraint(equalToConstant: 5), + mapPinShadow.heightAnchor.constraint(equalToConstant: 5), ]) - // Add custom search bar - self.view.addSubview(customSearchBar) + mapPin.setAnchorPoint(CGPoint(x: 0.5, y: 1)) + self.view.addSubview(mapPin) NSLayoutConstraint.activate([ - customSearchBar.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16), - customSearchBar.leadingAnchor.constraint( - equalTo: self.view.leadingAnchor, constant: 16), - customSearchBar.trailingAnchor.constraint( - equalTo: self.view.trailingAnchor, constant: -16), - customSearchBar.heightAnchor.constraint(equalToConstant: 44), + mapPin.centerXAnchor.constraint(equalTo: self.mapView.centerXAnchor), + mapPin.bottomAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.centerYAnchor, constant: 25), + mapPin.widthAnchor.constraint(equalToConstant: 50), + mapPin.heightAnchor.constraint(equalToConstant: 50), ]) - self.view.addSubview(searchResultContainer) NSLayoutConstraint.activate([ + searchResultContainer.widthAnchor.constraint(equalTo: self.view.widthAnchor), searchResultContainer.topAnchor.constraint( - equalTo: customSearchBar.bottomAnchor, constant: 8), - searchResultContainer.leadingAnchor.constraint( - equalTo: self.view.leadingAnchor, constant: 16), - searchResultContainer.trailingAnchor.constraint( - equalTo: self.view.trailingAnchor, constant: -16), - searchResultContainer.heightAnchor.constraint(lessThanOrEqualToConstant: 220), + equalTo: self.view.safeAreaLayoutGuide.topAnchor), + searchResultContainer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), ]) searchResultContainer.delegate = self + // MARK: - 2 Setup naivgation bar + setupNavigationBar() + } + private func setupNavigationBar() { + // MARK: - 1 Make cancel button (Shad CN secondary/outline) + let customCancelButton = UIButton(type: .system) + customCancelButton.addTarget(self, action: #selector(closePicker), for: .touchUpInside) + + if #available(iOS 15.0, *) { + var config = UIButton.Configuration.plain() + config.baseForegroundColor = UIColor(options.color) + // config.title = "Cancel" // Removed title + if #available(iOS 13.0, *) { + config.image = UIImage( + systemName: "xmark", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + // config.imagePadding = 8 // Not needed for icon-only + } + config.contentInsets = NSDirectionalEdgeInsets( + top: 8, leading: 8, bottom: 8, trailing: 8) // Symmetrical for icon + config.background.strokeColor = nil // Removed border + config.background.strokeWidth = 0 // Removed border + config.background.cornerRadius = 8 // Kept rounded corners for shape + customCancelButton.configuration = config + } else { + customCancelButton.tintColor = UIColor(options.color) + if #available(iOS 13.0, *) { + let cancelImage = UIImage( + systemName: "xmark", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + customCancelButton.setImage(cancelImage, for: .normal) + // customCancelButton.setTitle("Cancel", for: .normal) // Removed title + // customCancelButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: -4) // Not needed for icon-only + } else { + // customCancelButton.setTitle("Cancel", for: .normal) // Removed title + } + // customCancelButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold) // Not needed for icon-only + customCancelButton.layer.cornerRadius = 8 // Kept rounded corners for shape + customCancelButton.layer.borderColor = nil // Removed border + customCancelButton.layer.borderWidth = 0 // Removed border + customCancelButton.contentEdgeInsets = UIEdgeInsets( + top: 8, left: 8, bottom: 8, right: 8) // Symmetrical for icon + } - // Add buttons - self.view.addSubview(closeButton) - self.view.addSubview(doneButton) - - NSLayoutConstraint.activate([ - closeButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16), - closeButton.bottomAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -16), + // MARK: - 2 Make submit button (Shad CN primary) + let customSubmitButton = UIButton(type: .system) + customSubmitButton.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) + + if #available(iOS 15.0, *) { + var config = UIButton.Configuration.filled() + config.baseBackgroundColor = UIColor(options.color) + config.baseForegroundColor = UIColor(options.contrastColor) + config.title = "Submit" + config.contentInsets = NSDirectionalEdgeInsets( + top: 8, leading: 16, bottom: 8, trailing: 16) + config.background.cornerRadius = 16 // Made all rounded (half of estimated button height 32) + customSubmitButton.configuration = config + } else { + customSubmitButton.setTitle("Submit", for: .normal) + customSubmitButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold) + customSubmitButton.backgroundColor = UIColor(options.color) + customSubmitButton.tintColor = UIColor(options.contrastColor) + customSubmitButton.layer.cornerRadius = 16 // Made all rounded (half of estimated button height 32) + customSubmitButton.contentEdgeInsets = UIEdgeInsets( + top: 8, left: 16, bottom: 8, right: 16) + } - doneButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16), - doneButton.bottomAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -16), - ]) + // MARK: - 3 Make user location button (Shad CN secondary/icon-only) + let customUserLocationButton = UIButton(type: .system) + customUserLocationButton.addTarget( + self, action: #selector(pickUserLocation), for: .touchUpInside) + + if #available(iOS 15.0, *) { + var config = UIButton.Configuration.plain() + config.baseForegroundColor = UIColor(options.color) + if #available(iOS 13.0, *) { + config.image = UIImage( + systemName: "location", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + } + config.contentInsets = NSDirectionalEdgeInsets( + top: 8, leading: 8, bottom: 8, trailing: 8) + config.background.strokeColor = nil // Removed border + config.background.strokeWidth = 0 // Removed border + config.background.cornerRadius = 8 + customUserLocationButton.configuration = config + } else { + customUserLocationButton.tintColor = UIColor(options.color) + if #available(iOS 13.0, *) { + let locationImage = UIImage( + systemName: "location", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + customUserLocationButton.setImage(locationImage, for: .normal) + } else { + customUserLocationButton.setTitle("location", for: .normal) + } + customUserLocationButton.layer.cornerRadius = 8 + customUserLocationButton.layer.borderColor = nil // Removed border + customUserLocationButton.layer.borderWidth = 0 // Removed border + customUserLocationButton.contentEdgeInsets = UIEdgeInsets( + top: 8, left: 8, bottom: 8, right: 8) + } - if options.enableUserLocation { - self.view.addSubview(locationButton) - NSLayoutConstraint.activate([ - locationButton.trailingAnchor.constraint( - equalTo: self.view.trailingAnchor, constant: -16), - locationButton.topAnchor.constraint( - equalTo: searchResultContainer.bottomAnchor, constant: 16), - locationButton.widthAnchor.constraint(equalToConstant: 44), - locationButton.heightAnchor.constraint(equalToConstant: 44), - ]) + let customCancelButtonItem = UIBarButtonItem(customView: customCancelButton) + let customSubmitButtonItem = UIBarButtonItem(customView: customSubmitButton) + let customUserLocationButtonItem = UIBarButtonItem(customView: customUserLocationButton) + + if options.enableSearch { + if #available(iOS 13.0, *) { + searchController.automaticallyShowsCancelButton = true + searchController.searchBar.searchTextField.clearButtonMode = .whileEditing + searchController.searchBar.showsCancelButton = false + + // Shad CN search bar styling + searchController.searchBar.searchTextField.backgroundColor = + .secondarySystemBackground + searchController.searchBar.searchTextField.layer.cornerRadius = 8 + searchController.searchBar.searchTextField.clipsToBounds = true + searchController.searchBar.tintColor = UIColor(options.color) // Cursor tint + // Changed to systemBackground for consistency with the navigation bar background + searchController.searchBar.barTintColor = .systemBackground + searchController.searchBar.backgroundColor = .systemBackground + } else { + searchController.searchBar.setValue("OK", forKey: "cancelButtonText") + // Changed to systemBackground for consistency with the navigation bar background + searchController.searchBar.barTintColor = .systemBackground + searchController.searchBar.backgroundColor = .systemBackground + } + searchController.searchBar.placeholder = options.searchPlaceholder + searchController.searchBar.enablesReturnKeyAutomatically = true + searchController.searchBar.returnKeyType = .search + + searchController.searchResultsUpdater = self + searchController.searchBar.delegate = self + searchController.obscuresBackgroundDuringPresentation = false + searchController.hidesNavigationBarDuringPresentation = false // Keep search bar always visible + navigationItem.hidesSearchBarWhenScrolling = false + definesPresentationContext = true + navigationItem.searchController = searchController } - // Shadow under the pin - self.view.addSubview(mapPinShadow) - NSLayoutConstraint.activate([ - mapPinShadow.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), - mapPinShadow.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), - mapPinShadow.widthAnchor.constraint(equalToConstant: 8), - mapPinShadow.heightAnchor.constraint(equalToConstant: 8), - ]) + if options.enableLargeTitle { + self.navigationItem.largeTitleDisplayMode = .automatic + self.navigationController?.navigationBar.prefersLargeTitles = true + } else { + // Ensure title is inline if large title is not preferred, for consistency with search bar below title. + self.navigationItem.largeTitleDisplayMode = .never + self.navigationController?.navigationBar.prefersLargeTitles = false + } - // Add loading indicator for pin - pinLoading.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(pinLoading) - NSLayoutConstraint.activate([ - pinLoading.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), - pinLoading.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20), - pinLoading.widthAnchor.constraint(equalToConstant: 30), - pinLoading.heightAnchor.constraint(equalToConstant: 30), - ]) + var rightItems = [customSubmitButtonItem] + if options.enableUserLocation { + rightItems.append(customUserLocationButtonItem) + } + self.navigationItem.leftBarButtonItem = customCancelButtonItem + self.navigationItem.rightBarButtonItems = rightItems } // MARK: - UIViewController Lifecycle + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if #available(iOS 13, *) { + let appearance = UINavigationBarAppearance() + // Configure with a solid background color for visibility + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = .systemBackground // Use system background for visibility + appearance.shadowColor = nil // Remove any shadow line if desired + + // Ensure title and large title text are visible against the chosen background + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label] + appearance.titleTextAttributes = [.foregroundColor: UIColor.label] + + navigationController?.navigationBar.standardAppearance = appearance + navigationController?.navigationBar.scrollEdgeAppearance = appearance + navigationController?.navigationBar.compactAppearance = appearance + navigationController?.navigationBar.isTranslucent = false // Set to false with opaque background + } else { + // Fallback for older iOS: Make navigation bar opaque + self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default) + self.navigationController?.navigationBar.shadowImage = nil + self.navigationController?.navigationBar.isTranslucent = false + self.navigationController?.navigationBar.backgroundColor = .systemBackground // Use system background for visibility + } + } override func viewDidLoad() { super.viewDidLoad() + self.title = options.title if options.enableUserLocation { locationManager.delegate = self locationManager.requestWhenInUseAuthorization() completer.delegate = self } - navigationController?.setNavigationBarHidden(true, animated: false) setupViews() } - override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() mapView.centerCoordinate = CLLocationCoordinate2D( @@ -295,7 +347,6 @@ class PlacePickerViewController: UIViewController { longitude: options.initialCoordinates.longitude) mapView.delegate = self } - override func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() if !firstMapLoad { @@ -305,18 +356,16 @@ class PlacePickerViewController: UIViewController { firstMapLoad = false } } - override func viewDidDisappear(_ animated: Bool) { if promise != nil && options.rejectOnCancel { promise?.reject("dismissed", "Modal closed by user") } } - // MARK: - Button action methods + // MARK: - Navigation bar buttons methods @objc private func pickUserLocation() { locationManager.requestLocation() } - @objc private func closePicker() { if options.rejectOnCancel { if promise != nil { @@ -338,7 +387,6 @@ class PlacePickerViewController: UIViewController { self.dismiss(animated: true) } } - @objc private func finalizePicker() { let result = PlacePickerResult( coordinate: .init( @@ -356,17 +404,16 @@ class PlacePickerViewController: UIViewController { // MARK: - Private methods private func setLoading(_ state: Bool) { + pinImage.isHidden = state if state { pinLoading.startAnimating() } else { pinLoading.stopAnimating() } } - private func mapWillMove() { startPinAnimation() } - private func mapDidMove() { if options.enableGeocoding { setLoading(true) @@ -380,42 +427,61 @@ class PlacePickerViewController: UIViewController { self.setLoading(false) self.endPinAnimation() self.lastLocation = nil - self.customSearchBar.placeholder = self.options.searchPlaceholder + // Update search bar placeholder with default if error + self.navigationItem.searchController?.searchBar.placeholder = + self.options.searchPlaceholder return } self.lastLocation = location?.first if let name = location?.first?.name { - self.customSearchBar.placeholder = name + self.navigationItem.searchController?.searchBar.placeholder = name } else { - self.customSearchBar.placeholder = self.options.searchPlaceholder + self.navigationItem.searchController?.searchBar.placeholder = + self.options.searchPlaceholder } self.setLoading(false) self.endPinAnimation() + } } else { self.endPinAnimation() } } - private func startPinAnimation() { UIView.animate( - withDuration: 0.3, delay: 0, options: [.curveEaseOut], + withDuration: 0.3, delay: 0, options: [.curveEaseInOut], animations: { - // Use shadow animation to indicate pin movement - self.mapPinShadow.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) - self.mapPinShadow.alpha = 0.7 + self.mapPin.transform = CGAffineTransform.identity.scaledBy(x: 1.3, y: 1.3) + .translatedBy(x: 0, y: -10) }) } - private func endPinAnimation(_ comp: ((Bool) -> Void)? = nil) { + let rotationAmount: CGFloat = 0.5 UIView.animateKeyframes( - withDuration: 0.8, + withDuration: 1.8, delay: 0, - options: .calculationModeCubic, animations: { - UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { - self.mapPinShadow.transform = CGAffineTransform.identity - self.mapPinShadow.alpha = 1.0 + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1 / 6) { + self.mapPin.transform = CGAffineTransform.identity + } + UIView.addKeyframe(withRelativeStartTime: 1 / 6, relativeDuration: 1 / 6) { + self.mapPin.transform = CGAffineTransform.identity.rotated( + by: -rotationAmount / 2) + } + UIView.addKeyframe(withRelativeStartTime: 2 / 6, relativeDuration: 1 / 6) { + self.mapPin.transform = CGAffineTransform.identity.rotated( + by: rotationAmount / 3) + } + UIView.addKeyframe(withRelativeStartTime: 3 / 6, relativeDuration: 1 / 6) { + self.mapPin.transform = CGAffineTransform.identity.rotated( + by: -rotationAmount / 4) + } + UIView.addKeyframe(withRelativeStartTime: 4 / 6, relativeDuration: 1 / 6) { + self.mapPin.transform = CGAffineTransform.identity.rotated( + by: rotationAmount / 5) + } + UIView.addKeyframe(withRelativeStartTime: 5 / 6, relativeDuration: 1 / 6) { + self.mapPin.transform = CGAffineTransform.identity } }, completion: comp) } @@ -424,44 +490,32 @@ class PlacePickerViewController: UIViewController { extension PlacePickerViewController: MKMapViewDelegate { func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { mapDidMove() - } + } func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { mapWillMove() } -} +} extension PlacePickerViewController: UISearchBarDelegate { - func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { - if !searchText.isEmpty { - completer.queryFragment = searchText - } else { - completerResults.removeAll() - } - } - func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { self.completerResults.removeAll() return true } - func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - searchBar.resignFirstResponder() + // print("DID SEARCH") } } - extension PlacePickerViewController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { if let coordinate = locations.first?.coordinate { mapView.setCenter(coordinate, animated: true) } } - func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print(error) } } - extension PlacePickerViewController: DropDownButtonDelegate { func didSelect(_ index: Int) { let selectedResult = completerResults[index] @@ -477,13 +531,12 @@ extension PlacePickerViewController: DropDownButtonDelegate { return } self?.mapView.setCenter(coords, animated: true) - self?.customSearchBar.text = "" - self?.customSearchBar.resignFirstResponder() + self?.searchController.searchBar.text = "" + self?.searchController.isActive = false } } } } - extension PlacePickerViewController: MKLocalSearchCompleterDelegate { func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { completerResults = completer.results.map { r in @@ -494,7 +547,6 @@ extension PlacePickerViewController: MKLocalSearchCompleterDelegate { } } } - extension PlacePickerViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { if let searchText = searchController.searchBar.text, !searchText.isEmpty { From 182c7461c4b184ba2c91d457370ea2aeb4296129 Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Thu, 25 Sep 2025 00:27:08 +0530 Subject: [PATCH 4/8] changes --- ios/PlacePickerViewController.swift | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index 16891ec..b90b5e8 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -157,18 +157,16 @@ class PlacePickerViewController: UIViewController { if #available(iOS 15.0, *) { var config = UIButton.Configuration.plain() config.baseForegroundColor = UIColor(options.color) - // config.title = "Cancel" // Removed title if #available(iOS 13.0, *) { config.image = UIImage( systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) - // config.imagePadding = 8 // Not needed for icon-only } config.contentInsets = NSDirectionalEdgeInsets( - top: 8, leading: 8, bottom: 8, trailing: 8) // Symmetrical for icon - config.background.strokeColor = nil // Removed border - config.background.strokeWidth = 0 // Removed border - config.background.cornerRadius = 8 // Kept rounded corners for shape + top: 8, leading: 8, bottom: 8, trailing: 8) + config.background.strokeColor = nil + config.background.strokeWidth = 0 + config.background.cornerRadius = 8 customCancelButton.configuration = config } else { customCancelButton.tintColor = UIColor(options.color) @@ -177,11 +175,13 @@ class PlacePickerViewController: UIViewController { systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) customCancelButton.setImage(cancelImage, for: .normal) - // customCancelButton.setTitle("Cancel", for: .normal) // Removed title - // customCancelButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: -4) // Not needed for icon-only } else { - // customCancelButton.setTitle("Cancel", for: .normal) // Removed title + // For iOS versions prior to 13, system images are not available. + // Ensure a title is set and give it a background for better visibility. + customCancelButton.setTitle("Cancel", for: .normal) } + // Add a background color for older iOS versions to make the button stand out + customCancelButton.backgroundColor = UIColor(white: 0.95, alpha: 1.0) // Light gray background // customCancelButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold) // Not needed for icon-only customCancelButton.layer.cornerRadius = 8 // Kept rounded corners for shape customCancelButton.layer.borderColor = nil // Removed border @@ -228,8 +228,8 @@ class PlacePickerViewController: UIViewController { } config.contentInsets = NSDirectionalEdgeInsets( top: 8, leading: 8, bottom: 8, trailing: 8) - config.background.strokeColor = nil // Removed border - config.background.strokeWidth = 0 // Removed border + config.background.strokeColor = nil + config.background.strokeWidth = 0 config.background.cornerRadius = 8 customUserLocationButton.configuration = config } else { @@ -240,8 +240,12 @@ class PlacePickerViewController: UIViewController { withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) customUserLocationButton.setImage(locationImage, for: .normal) } else { - customUserLocationButton.setTitle("location", for: .normal) + // For iOS versions prior to 13, system images are not available. + // Ensure a title is set and give it a background for better visibility. + customUserLocationButton.setTitle("Location", for: .normal) // Ensure title is set } + // Add a background color for older iOS versions to make the button stand out + customUserLocationButton.backgroundColor = UIColor(white: 0.95, alpha: 1.0) // Light gray background customUserLocationButton.layer.cornerRadius = 8 customUserLocationButton.layer.borderColor = nil // Removed border customUserLocationButton.layer.borderWidth = 0 // Removed border From 753bbdbd49588a69b242978a9b605272ab7b9ba7 Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Thu, 25 Sep 2025 13:35:49 +0530 Subject: [PATCH 5/8] changes --- ios/PlacePickerViewController.swift | 99 ++++++++++++++++++----------- 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index b90b5e8..c252f34 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -27,6 +27,20 @@ class PlacePickerViewController: UIViewController { private let geocoder = CLGeocoder() private let locationManager = CLLocationManager() + // MARK: - Custom Colors + // A primary accent color that adapts to light and dark modes. + // For light mode: Black + // For dark mode: White + private lazy var accentColor: UIColor = { + return .label // System color that is black in light mode, white in dark mode + }() + + // A contrasting color to be used on top of the accent color. + // White generally provides good contrast against black. + private lazy var onAccentColor: UIColor = { + return .systemBackground // System color that is white in light mode, black in dark mode + }() + // MARK: - Inits init(_ options: PlacePickerOptions, _ promise: Promise) { self.promise = promise @@ -40,7 +54,7 @@ class PlacePickerViewController: UIViewController { // MARK: - UI Views private lazy var mapPinShadow: UIView = { let shadowView = UIView() - shadowView.backgroundColor = UIColor(options.color).withAlphaComponent(0.5) + shadowView.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Black shadow shadowView.translatesAutoresizingMaskIntoConstraints = false shadowView.layer.cornerRadius = 2.5 return shadowView @@ -53,20 +67,20 @@ class PlacePickerViewController: UIViewController { pinImage = UIImageView(image: UIImage(named: "mappin")) } pinImage.contentMode = .center - pinImage.tintColor = UIColor(options.contrastColor) + pinImage.tintColor = onAccentColor // White in light, black in dark pinImage.frame = CGRect(x: 0, y: 0, width: 40, height: 40) return pinImage }() private lazy var pinLoading: UIActivityIndicatorView = { let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) - loader.color = UIColor(options.contrastColor) + loader.color = onAccentColor // White in light, black in dark loader.hidesWhenStopped = true return loader }() private lazy var mapPinContentView: UIView = { let pinContainer = UIView(frame: CGRect(x: 5, y: 4, width: 40, height: 40)) pinContainer.layer.cornerRadius = 20 - pinContainer.backgroundColor = UIColor(options.color) + pinContainer.backgroundColor = accentColor // Black in light, white in dark pinContainer.addSubview(pinImage) pinContainer.addSubview(pinLoading) return pinContainer @@ -80,7 +94,7 @@ class PlacePickerViewController: UIViewController { path.addLine(to: CGPoint(x: 20, y: 43)) let shape = CAShapeLayer() shape.path = path - shape.fillColor = UIColor(options.color).cgColor + shape.fillColor = accentColor.cgColor // Black in light, white in dark let pinView = UIView() pinView.layer.insertSublayer(shape, at: 0) pinView.addSubview(mapPinContentView) @@ -156,7 +170,7 @@ class PlacePickerViewController: UIViewController { if #available(iOS 15.0, *) { var config = UIButton.Configuration.plain() - config.baseForegroundColor = UIColor(options.color) + config.baseForegroundColor = accentColor // Black in light, white in dark if #available(iOS 13.0, *) { config.image = UIImage( systemName: "xmark", @@ -169,25 +183,29 @@ class PlacePickerViewController: UIViewController { config.background.cornerRadius = 8 customCancelButton.configuration = config } else { - customCancelButton.tintColor = UIColor(options.color) + // This block applies to iOS versions below 15.0 + customCancelButton.tintColor = accentColor // Black in light, white in dark if #available(iOS 13.0, *) { + // This block applies to iOS versions 13.0 - 14.x let cancelImage = UIImage( systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) customCancelButton.setImage(cancelImage, for: .normal) } else { + // This block applies to iOS versions below 13.0 (i.e., iOS 12 and earlier) // For iOS versions prior to 13, system images are not available. // Ensure a title is set and give it a background for better visibility. customCancelButton.setTitle("Cancel", for: .normal) + // Change tintColor (text color) to match foreground (black/white) + customCancelButton.tintColor = .label } - // Add a background color for older iOS versions to make the button stand out - customCancelButton.backgroundColor = UIColor(white: 0.95, alpha: 1.0) // Light gray background - // customCancelButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold) // Not needed for icon-only - customCancelButton.layer.cornerRadius = 8 // Kept rounded corners for shape - customCancelButton.layer.borderColor = nil // Removed border - customCancelButton.layer.borderWidth = 0 // Removed border + // Change background color for older iOS versions to clear + customCancelButton.backgroundColor = .clear + customCancelButton.layer.cornerRadius = 8 + customCancelButton.layer.borderColor = nil + customCancelButton.layer.borderWidth = 0 customCancelButton.contentEdgeInsets = UIEdgeInsets( - top: 8, left: 8, bottom: 8, right: 8) // Symmetrical for icon + top: 8, left: 8, bottom: 8, right: 8) } // MARK: - 2 Make submit button (Shad CN primary) @@ -196,19 +214,21 @@ class PlacePickerViewController: UIViewController { if #available(iOS 15.0, *) { var config = UIButton.Configuration.filled() - config.baseBackgroundColor = UIColor(options.color) - config.baseForegroundColor = UIColor(options.contrastColor) + config.baseBackgroundColor = accentColor // Black in light, white in dark + config.baseForegroundColor = onAccentColor // White in light, black in dark config.title = "Submit" config.contentInsets = NSDirectionalEdgeInsets( top: 8, leading: 16, bottom: 8, trailing: 16) - config.background.cornerRadius = 16 // Made all rounded (half of estimated button height 32) + config.background.cornerRadius = 16 customSubmitButton.configuration = config } else { + // This block applies to iOS versions below 15.0 (includes below 13) customSubmitButton.setTitle("Submit", for: .normal) customSubmitButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold) - customSubmitButton.backgroundColor = UIColor(options.color) - customSubmitButton.tintColor = UIColor(options.contrastColor) - customSubmitButton.layer.cornerRadius = 16 // Made all rounded (half of estimated button height 32) + // Change background color to match accent color (black/white) + customSubmitButton.backgroundColor = accentColor + customSubmitButton.tintColor = onAccentColor // White in light, black in dark + customSubmitButton.layer.cornerRadius = 16 customSubmitButton.contentEdgeInsets = UIEdgeInsets( top: 8, left: 16, bottom: 8, right: 16) } @@ -220,7 +240,7 @@ class PlacePickerViewController: UIViewController { if #available(iOS 15.0, *) { var config = UIButton.Configuration.plain() - config.baseForegroundColor = UIColor(options.color) + config.baseForegroundColor = accentColor // Black in light, white in dark if #available(iOS 13.0, *) { config.image = UIImage( systemName: "location", @@ -233,22 +253,27 @@ class PlacePickerViewController: UIViewController { config.background.cornerRadius = 8 customUserLocationButton.configuration = config } else { - customUserLocationButton.tintColor = UIColor(options.color) + // This block applies to iOS versions below 15.0 + customUserLocationButton.tintColor = accentColor // Black in light, white in dark if #available(iOS 13.0, *) { + // This block applies to iOS versions 13.0 - 14.x let locationImage = UIImage( systemName: "location", withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) customUserLocationButton.setImage(locationImage, for: .normal) } else { + // This block applies to iOS versions below 13.0 (i.e., iOS 12 and earlier) // For iOS versions prior to 13, system images are not available. // Ensure a title is set and give it a background for better visibility. - customUserLocationButton.setTitle("Location", for: .normal) // Ensure title is set + customUserLocationButton.setTitle("Location", for: .normal) + // Change tintColor (text color) to match foreground (black/white) + customUserLocationButton.tintColor = .label } - // Add a background color for older iOS versions to make the button stand out - customUserLocationButton.backgroundColor = UIColor(white: 0.95, alpha: 1.0) // Light gray background + // Change background color for older iOS versions to clear + customUserLocationButton.backgroundColor = .clear customUserLocationButton.layer.cornerRadius = 8 - customUserLocationButton.layer.borderColor = nil // Removed border - customUserLocationButton.layer.borderWidth = 0 // Removed border + customUserLocationButton.layer.borderColor = nil + customUserLocationButton.layer.borderWidth = 0 customUserLocationButton.contentEdgeInsets = UIEdgeInsets( top: 8, left: 8, bottom: 8, right: 8) } @@ -265,18 +290,18 @@ class PlacePickerViewController: UIViewController { // Shad CN search bar styling searchController.searchBar.searchTextField.backgroundColor = - .secondarySystemBackground + .secondarySystemBackground // Already grayscale searchController.searchBar.searchTextField.layer.cornerRadius = 8 searchController.searchBar.searchTextField.clipsToBounds = true - searchController.searchBar.tintColor = UIColor(options.color) // Cursor tint + searchController.searchBar.tintColor = accentColor // Cursor tint (black/white) // Changed to systemBackground for consistency with the navigation bar background - searchController.searchBar.barTintColor = .systemBackground - searchController.searchBar.backgroundColor = .systemBackground + searchController.searchBar.barTintColor = .systemBackground // Already grayscale + searchController.searchBar.backgroundColor = .systemBackground // Already grayscale } else { searchController.searchBar.setValue("OK", forKey: "cancelButtonText") // Changed to systemBackground for consistency with the navigation bar background - searchController.searchBar.barTintColor = .systemBackground - searchController.searchBar.backgroundColor = .systemBackground + searchController.searchBar.barTintColor = .systemBackground // Already grayscale + searchController.searchBar.backgroundColor = .systemBackground // Already grayscale } searchController.searchBar.placeholder = options.searchPlaceholder searchController.searchBar.enablesReturnKeyAutomatically = true @@ -315,12 +340,12 @@ class PlacePickerViewController: UIViewController { let appearance = UINavigationBarAppearance() // Configure with a solid background color for visibility appearance.configureWithOpaqueBackground() - appearance.backgroundColor = .systemBackground // Use system background for visibility + appearance.backgroundColor = .systemBackground // Use system background for visibility (already grayscale) appearance.shadowColor = nil // Remove any shadow line if desired // Ensure title and large title text are visible against the chosen background - appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label] - appearance.titleTextAttributes = [.foregroundColor: UIColor.label] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label] // Already grayscale + appearance.titleTextAttributes = [.foregroundColor: UIColor.label] // Already grayscale navigationController?.navigationBar.standardAppearance = appearance navigationController?.navigationBar.scrollEdgeAppearance = appearance @@ -331,7 +356,7 @@ class PlacePickerViewController: UIViewController { self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default) self.navigationController?.navigationBar.shadowImage = nil self.navigationController?.navigationBar.isTranslucent = false - self.navigationController?.navigationBar.backgroundColor = .systemBackground // Use system background for visibility + self.navigationController?.navigationBar.backgroundColor = .systemBackground // Use system background for visibility (already grayscale) } } override func viewDidLoad() { From 18a702884254591b59e1314d92a8070e8f52ff14 Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Sun, 28 Sep 2025 14:58:56 +0530 Subject: [PATCH 6/8] changes --- ios/PlacePickerViewController.swift | 258 +++++++++++----------------- 1 file changed, 97 insertions(+), 161 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index c252f34..9169ec8 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -27,20 +27,6 @@ class PlacePickerViewController: UIViewController { private let geocoder = CLGeocoder() private let locationManager = CLLocationManager() - // MARK: - Custom Colors - // A primary accent color that adapts to light and dark modes. - // For light mode: Black - // For dark mode: White - private lazy var accentColor: UIColor = { - return .label // System color that is black in light mode, white in dark mode - }() - - // A contrasting color to be used on top of the accent color. - // White generally provides good contrast against black. - private lazy var onAccentColor: UIColor = { - return .systemBackground // System color that is white in light mode, black in dark mode - }() - // MARK: - Inits init(_ options: PlacePickerOptions, _ promise: Promise) { self.promise = promise @@ -54,7 +40,8 @@ class PlacePickerViewController: UIViewController { // MARK: - UI Views private lazy var mapPinShadow: UIView = { let shadowView = UIView() - shadowView.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Black shadow + // Use a fixed black for shadow, as it generally looks good on any background + shadowView.backgroundColor = UIColor.black.withAlphaComponent(0.3) shadowView.translatesAutoresizingMaskIntoConstraints = false shadowView.layer.cornerRadius = 2.5 return shadowView @@ -67,20 +54,23 @@ class PlacePickerViewController: UIViewController { pinImage = UIImageView(image: UIImage(named: "mappin")) } pinImage.contentMode = .center - pinImage.tintColor = onAccentColor // White in light, black in dark + // Use system background color for contrast with the pin's label color + pinImage.tintColor = .systemBackground pinImage.frame = CGRect(x: 0, y: 0, width: 40, height: 40) return pinImage }() private lazy var pinLoading: UIActivityIndicatorView = { let loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) - loader.color = onAccentColor // White in light, black in dark + // Use system background color for contrast with the pin's label color + loader.color = .systemBackground loader.hidesWhenStopped = true return loader }() private lazy var mapPinContentView: UIView = { let pinContainer = UIView(frame: CGRect(x: 5, y: 4, width: 40, height: 40)) pinContainer.layer.cornerRadius = 20 - pinContainer.backgroundColor = accentColor // Black in light, white in dark + // Use label color (adaptive black/white) for the pin's main body + pinContainer.backgroundColor = .label pinContainer.addSubview(pinImage) pinContainer.addSubview(pinLoading) return pinContainer @@ -94,7 +84,8 @@ class PlacePickerViewController: UIViewController { path.addLine(to: CGPoint(x: 20, y: 43)) let shape = CAShapeLayer() shape.path = path - shape.fillColor = accentColor.cgColor // Black in light, white in dark + // Use label color (adaptive black/white) for the pin's triangle + shape.fillColor = UIColor.label.cgColor let pinView = UIView() pinView.layer.insertSublayer(shape, at: 0) pinView.addSubview(mapPinContentView) @@ -132,6 +123,8 @@ class PlacePickerViewController: UIViewController { NSLayoutConstraint.activate([ mapView.widthAnchor.constraint(equalTo: self.view.widthAnchor), mapView.heightAnchor.constraint(equalTo: self.view.heightAnchor), + // mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + // mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) self.view.addSubview(mapPinShadow) NSLayoutConstraint.activate([ @@ -164,122 +157,58 @@ class PlacePickerViewController: UIViewController { setupNavigationBar() } private func setupNavigationBar() { - // MARK: - 1 Make cancel button (Shad CN secondary/outline) - let customCancelButton = UIButton(type: .system) + // MARK: - 1 Make cancel button + let customCancelButton = UIButton() + // Use label color (adaptive black/white) for button tint + customCancelButton.tintColor = .label + if #available(iOS 13.0, *) { + let cancelImage = UIImage( + systemName: "xmark", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + customCancelButton.setImage(cancelImage, for: .normal) + } else { + customCancelButton.setTitle("Cancel", for: .normal) + } customCancelButton.addTarget(self, action: #selector(closePicker), for: .touchUpInside) - if #available(iOS 15.0, *) { - var config = UIButton.Configuration.plain() - config.baseForegroundColor = accentColor // Black in light, white in dark - if #available(iOS 13.0, *) { - config.image = UIImage( - systemName: "xmark", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) - } - config.contentInsets = NSDirectionalEdgeInsets( - top: 8, leading: 8, bottom: 8, trailing: 8) - config.background.strokeColor = nil - config.background.strokeWidth = 0 - config.background.cornerRadius = 8 - customCancelButton.configuration = config + // MARK: - 2 Make done button + let customDoneButton = UIButton() + // Use label color (adaptive black/white) for button tint + customDoneButton.tintColor = .label + if #available(iOS 13.0, *) { + let checkImage = UIImage( + systemName: "checkmark", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + customDoneButton.setImage(checkImage, for: .normal) } else { - // This block applies to iOS versions below 15.0 - customCancelButton.tintColor = accentColor // Black in light, white in dark - if #available(iOS 13.0, *) { - // This block applies to iOS versions 13.0 - 14.x - let cancelImage = UIImage( - systemName: "xmark", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) - customCancelButton.setImage(cancelImage, for: .normal) - } else { - // This block applies to iOS versions below 13.0 (i.e., iOS 12 and earlier) - // For iOS versions prior to 13, system images are not available. - // Ensure a title is set and give it a background for better visibility. - customCancelButton.setTitle("Cancel", for: .normal) - // Change tintColor (text color) to match foreground (black/white) - customCancelButton.tintColor = .label - } - // Change background color for older iOS versions to clear - customCancelButton.backgroundColor = .clear - customCancelButton.layer.cornerRadius = 8 - customCancelButton.layer.borderColor = nil - customCancelButton.layer.borderWidth = 0 - customCancelButton.contentEdgeInsets = UIEdgeInsets( - top: 8, left: 8, bottom: 8, right: 8) + customDoneButton.setTitle("Done", for: .normal) } + customDoneButton.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) - // MARK: - 2 Make submit button (Shad CN primary) - let customSubmitButton = UIButton(type: .system) - customSubmitButton.addTarget(self, action: #selector(finalizePicker), for: .touchUpInside) - - if #available(iOS 15.0, *) { - var config = UIButton.Configuration.filled() - config.baseBackgroundColor = accentColor // Black in light, white in dark - config.baseForegroundColor = onAccentColor // White in light, black in dark - config.title = "Submit" - config.contentInsets = NSDirectionalEdgeInsets( - top: 8, leading: 16, bottom: 8, trailing: 16) - config.background.cornerRadius = 16 - customSubmitButton.configuration = config + // MARK: - 3 Make user location button + let customUserLocationButton = UIButton() + // Use label color (adaptive black/white) for button tint + customUserLocationButton.tintColor = .label + if #available(iOS 13.0, *) { + let checkImage = UIImage( + systemName: "location", + withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) + customUserLocationButton.setImage(checkImage, for: .normal) } else { - // This block applies to iOS versions below 15.0 (includes below 13) - customSubmitButton.setTitle("Submit", for: .normal) - customSubmitButton.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold) - // Change background color to match accent color (black/white) - customSubmitButton.backgroundColor = accentColor - customSubmitButton.tintColor = onAccentColor // White in light, black in dark - customSubmitButton.layer.cornerRadius = 16 - customSubmitButton.contentEdgeInsets = UIEdgeInsets( - top: 8, left: 16, bottom: 8, right: 16) + customUserLocationButton.setTitle("location", for: .normal) } - - // MARK: - 3 Make user location button (Shad CN secondary/icon-only) - let customUserLocationButton = UIButton(type: .system) customUserLocationButton.addTarget( self, action: #selector(pickUserLocation), for: .touchUpInside) if #available(iOS 15.0, *) { - var config = UIButton.Configuration.plain() - config.baseForegroundColor = accentColor // Black in light, white in dark - if #available(iOS 13.0, *) { - config.image = UIImage( - systemName: "location", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) - } - config.contentInsets = NSDirectionalEdgeInsets( - top: 8, leading: 8, bottom: 8, trailing: 8) - config.background.strokeColor = nil - config.background.strokeWidth = 0 - config.background.cornerRadius = 8 - customUserLocationButton.configuration = config - } else { - // This block applies to iOS versions below 15.0 - customUserLocationButton.tintColor = accentColor // Black in light, white in dark - if #available(iOS 13.0, *) { - // This block applies to iOS versions 13.0 - 14.x - let locationImage = UIImage( - systemName: "location", - withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .bold)) - customUserLocationButton.setImage(locationImage, for: .normal) - } else { - // This block applies to iOS versions below 13.0 (i.e., iOS 12 and earlier) - // For iOS versions prior to 13, system images are not available. - // Ensure a title is set and give it a background for better visibility. - customUserLocationButton.setTitle("Location", for: .normal) - // Change tintColor (text color) to match foreground (black/white) - customUserLocationButton.tintColor = .label - } - // Change background color for older iOS versions to clear - customUserLocationButton.backgroundColor = .clear - customUserLocationButton.layer.cornerRadius = 8 - customUserLocationButton.layer.borderColor = nil - customUserLocationButton.layer.borderWidth = 0 - customUserLocationButton.contentEdgeInsets = UIEdgeInsets( - top: 8, left: 8, bottom: 8, right: 8) + // These configurations will automatically pick up the tintColor + customDoneButton.configuration = .borderedTinted() + customCancelButton.configuration = .bordered() // Changed from .borderedProminent() to match other buttons' style + customUserLocationButton.configuration = .bordered() } let customCancelButtonItem = UIBarButtonItem(customView: customCancelButton) - let customSubmitButtonItem = UIBarButtonItem(customView: customSubmitButton) + let customDoneButtonItem = UIBarButtonItem(customView: customDoneButton) let customUserLocationButtonItem = UIBarButtonItem(customView: customUserLocationButton) if options.enableSearch { @@ -288,29 +217,24 @@ class PlacePickerViewController: UIViewController { searchController.searchBar.searchTextField.clearButtonMode = .whileEditing searchController.searchBar.showsCancelButton = false - // Shad CN search bar styling + // Black and white theme for search bar elements + searchController.searchBar.searchTextField.textColor = .label searchController.searchBar.searchTextField.backgroundColor = - .secondarySystemBackground // Already grayscale - searchController.searchBar.searchTextField.layer.cornerRadius = 8 - searchController.searchBar.searchTextField.clipsToBounds = true - searchController.searchBar.tintColor = accentColor // Cursor tint (black/white) - // Changed to systemBackground for consistency with the navigation bar background - searchController.searchBar.barTintColor = .systemBackground // Already grayscale - searchController.searchBar.backgroundColor = .systemBackground // Already grayscale + .secondarySystemBackground + searchController.searchBar.searchTextField.leftView?.tintColor = .label // Search icon } else { searchController.searchBar.setValue("OK", forKey: "cancelButtonText") - // Changed to systemBackground for consistency with the navigation bar background - searchController.searchBar.barTintColor = .systemBackground // Already grayscale - searchController.searchBar.backgroundColor = .systemBackground // Already grayscale } searchController.searchBar.placeholder = options.searchPlaceholder searchController.searchBar.enablesReturnKeyAutomatically = true searchController.searchBar.returnKeyType = .search + searchController.searchBar.tintColor = .label // Cursor color and potential cancel button + searchController.searchBar.barTintColor = .systemBackground // Background behind the search field searchController.searchResultsUpdater = self searchController.searchBar.delegate = self searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = false // Keep search bar always visible + searchController.hidesNavigationBarDuringPresentation = false navigationItem.hidesSearchBarWhenScrolling = false definesPresentationContext = true navigationItem.searchController = searchController @@ -319,13 +243,8 @@ class PlacePickerViewController: UIViewController { if options.enableLargeTitle { self.navigationItem.largeTitleDisplayMode = .automatic self.navigationController?.navigationBar.prefersLargeTitles = true - } else { - // Ensure title is inline if large title is not preferred, for consistency with search bar below title. - self.navigationItem.largeTitleDisplayMode = .never - self.navigationController?.navigationBar.prefersLargeTitles = false } - - var rightItems = [customSubmitButtonItem] + var rightItems = [customDoneButtonItem] if options.enableUserLocation { rightItems.append(customUserLocationButtonItem) } @@ -335,33 +254,29 @@ class PlacePickerViewController: UIViewController { // MARK: - UIViewController Lifecycle override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) if #available(iOS 13, *) { let appearance = UINavigationBarAppearance() - // Configure with a solid background color for visibility + // Configure with an opaque background to prevent transparency appearance.configureWithOpaqueBackground() - appearance.backgroundColor = .systemBackground // Use system background for visibility (already grayscale) - appearance.shadowColor = nil // Remove any shadow line if desired - - // Ensure title and large title text are visible against the chosen background - appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label] // Already grayscale - appearance.titleTextAttributes = [.foregroundColor: UIColor.label] // Already grayscale + // Explicitly set background color to system background (adaptive black/white) + appearance.backgroundColor = .systemBackground + // Set title text attributes to label color for black/white theme + appearance.titleTextAttributes = [.foregroundColor: UIColor.label] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label] + // Optionally remove the shadow line under the navigation bar + appearance.shadowColor = .clear navigationController?.navigationBar.standardAppearance = appearance navigationController?.navigationBar.scrollEdgeAppearance = appearance - navigationController?.navigationBar.compactAppearance = appearance - navigationController?.navigationBar.isTranslucent = false // Set to false with opaque background - } else { - // Fallback for older iOS: Make navigation bar opaque - self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default) - self.navigationController?.navigationBar.shadowImage = nil - self.navigationController?.navigationBar.isTranslucent = false - self.navigationController?.navigationBar.backgroundColor = .systemBackground // Use system background for visibility (already grayscale) + // Ensure the navigation bar is not translucent to prevent blurring map content + navigationController?.navigationBar.isTranslucent = false } } override func viewDidLoad() { super.viewDidLoad() self.title = options.title + // Set the view's background color to system background for consistency + self.view.backgroundColor = .systemBackground if options.enableUserLocation { locationManager.delegate = self locationManager.requestWhenInUseAuthorization() @@ -456,17 +371,38 @@ class PlacePickerViewController: UIViewController { self.setLoading(false) self.endPinAnimation() self.lastLocation = nil - // Update search bar placeholder with default if error - self.navigationItem.searchController?.searchBar.placeholder = - self.options.searchPlaceholder + // Ensure search bar placeholder text color is readable + if #available(iOS 13.0, *) { + self.navigationItem.searchController?.searchBar.searchTextField + .attributedPlaceholder = NSAttributedString( + string: self.options.searchPlaceholder, + attributes: [.foregroundColor: UIColor.secondaryLabel]) + } else { + self.navigationItem.searchController?.searchBar.placeholder = + self.options.searchPlaceholder + } return } self.lastLocation = location?.first if let name = location?.first?.name { - self.navigationItem.searchController?.searchBar.placeholder = name + if #available(iOS 13.0, *) { + self.navigationItem.searchController?.searchBar.searchTextField + .attributedPlaceholder = NSAttributedString( + string: name, attributes: [.foregroundColor: UIColor.secondaryLabel] + ) + } else { + self.navigationItem.searchController?.searchBar.placeholder = name + } } else { - self.navigationItem.searchController?.searchBar.placeholder = - self.options.searchPlaceholder + if #available(iOS 13.0, *) { + self.navigationItem.searchController?.searchBar.searchTextField + .attributedPlaceholder = NSAttributedString( + string: self.options.searchPlaceholder, + attributes: [.foregroundColor: UIColor.secondaryLabel]) + } else { + self.navigationItem.searchController?.searchBar.placeholder = + self.options.searchPlaceholder + } } self.setLoading(false) self.endPinAnimation() From fb6f33a9a5f6cb729b0366ec958c4d0f427d304c Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Sun, 5 Oct 2025 14:09:07 +0530 Subject: [PATCH 7/8] changes --- ios/PlacePickerViewController.swift | 178 +++++++++++++++++----------- 1 file changed, 112 insertions(+), 66 deletions(-) diff --git a/ios/PlacePickerViewController.swift b/ios/PlacePickerViewController.swift index 9169ec8..b33241c 100644 --- a/ios/PlacePickerViewController.swift +++ b/ios/PlacePickerViewController.swift @@ -13,7 +13,7 @@ class PlacePickerViewController: UIViewController { // MARK: - Variables private var promise: Promise? private let options: PlacePickerOptions - private let searchController = UISearchController() + // Removed: private let searchController = UISearchController() private let completer = MKLocalSearchCompleter() private var completerResults: [CustomSearchCompletion] = [] { didSet { @@ -26,6 +26,7 @@ class PlacePickerViewController: UIViewController { private var mapMoveDebounceTimer: Timer? private let geocoder = CLGeocoder() private let locationManager = CLLocationManager() + private var shouldCenterMapOnUserLocationUpdate: Bool = false // New flag to control map centering // MARK: - Inits init(_ options: PlacePickerOptions, _ promise: Promise) { @@ -116,6 +117,34 @@ class PlacePickerViewController: UIViewController { return view }() + private lazy var searchTextField: UITextField = { + let textField = UITextField() + textField.placeholder = options.searchPlaceholder + textField.returnKeyType = .search + textField.enablesReturnKeyAutomatically = true + textField.clearButtonMode = .whileEditing + textField.translatesAutoresizingMaskIntoConstraints = false + + if #available(iOS 13.0, *) { + textField.textColor = .label + textField.backgroundColor = .secondarySystemBackground + // Add search icon + let searchIcon = UIImageView(image: UIImage(systemName: "magnifyingglass")) + searchIcon.tintColor = .label + searchIcon.contentMode = .scaleAspectFit + let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 20)) + searchIcon.frame = CGRect(x: 5, y: 0, width: 20, height: 20) + leftView.addSubview(searchIcon) + textField.leftView = leftView + textField.leftViewMode = .always + textField.layer.cornerRadius = 10 // Rounded corners for modern look + textField.clipsToBounds = true + } else { + textField.borderStyle = .roundedRect + } + return textField + }() + // MARK: - UI setup methods private func setupViews() { // MARK: - 1 Setup map view @@ -123,9 +152,30 @@ class PlacePickerViewController: UIViewController { NSLayoutConstraint.activate([ mapView.widthAnchor.constraint(equalTo: self.view.widthAnchor), mapView.heightAnchor.constraint(equalTo: self.view.heightAnchor), - // mapView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), - // mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) + + var topAnchorForSearchResultContainer: NSLayoutYAxisAnchor = self.view.safeAreaLayoutGuide + .topAnchor + + if options.enableSearch { + // Add search text field below the navigation bar with padding + self.view.addSubview(searchTextField) + searchTextField.delegate = self + searchTextField.addTarget( + self, action: #selector(searchTextFieldDidChange(_:)), for: .editingChanged) + + NSLayoutConstraint.activate([ + searchTextField.topAnchor.constraint( + equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 10), // Padding from top + searchTextField.leadingAnchor.constraint( + equalTo: self.view.leadingAnchor, constant: 16), // Left padding + searchTextField.trailingAnchor.constraint( + equalTo: self.view.trailingAnchor, constant: -16), // Right padding + searchTextField.heightAnchor.constraint(equalToConstant: 40), // Standard height + ]) + topAnchorForSearchResultContainer = searchTextField.bottomAnchor + } + self.view.addSubview(mapPinShadow) NSLayoutConstraint.activate([ mapPinShadow.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), @@ -144,11 +194,12 @@ class PlacePickerViewController: UIViewController { mapPin.widthAnchor.constraint(equalToConstant: 50), mapPin.heightAnchor.constraint(equalToConstant: 50), ]) + self.view.addSubview(searchResultContainer) NSLayoutConstraint.activate([ - searchResultContainer.widthAnchor.constraint(equalTo: self.view.widthAnchor), - searchResultContainer.topAnchor.constraint( - equalTo: self.view.safeAreaLayoutGuide.topAnchor), + searchResultContainer.topAnchor.constraint(equalTo: topAnchorForSearchResultContainer), + searchResultContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + searchResultContainer.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), searchResultContainer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), ]) @@ -203,7 +254,7 @@ class PlacePickerViewController: UIViewController { if #available(iOS 15.0, *) { // These configurations will automatically pick up the tintColor customDoneButton.configuration = .borderedTinted() - customCancelButton.configuration = .bordered() // Changed from .borderedProminent() to match other buttons' style + customCancelButton.configuration = .bordered() customUserLocationButton.configuration = .bordered() } @@ -212,32 +263,9 @@ class PlacePickerViewController: UIViewController { let customUserLocationButtonItem = UIBarButtonItem(customView: customUserLocationButton) if options.enableSearch { - if #available(iOS 13.0, *) { - searchController.automaticallyShowsCancelButton = true - searchController.searchBar.searchTextField.clearButtonMode = .whileEditing - searchController.searchBar.showsCancelButton = false - - // Black and white theme for search bar elements - searchController.searchBar.searchTextField.textColor = .label - searchController.searchBar.searchTextField.backgroundColor = - .secondarySystemBackground - searchController.searchBar.searchTextField.leftView?.tintColor = .label // Search icon - } else { - searchController.searchBar.setValue("OK", forKey: "cancelButtonText") - } - searchController.searchBar.placeholder = options.searchPlaceholder - searchController.searchBar.enablesReturnKeyAutomatically = true - searchController.searchBar.returnKeyType = .search - searchController.searchBar.tintColor = .label // Cursor color and potential cancel button - searchController.searchBar.barTintColor = .systemBackground // Background behind the search field - - searchController.searchResultsUpdater = self - searchController.searchBar.delegate = self - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = false - navigationItem.hidesSearchBarWhenScrolling = false - definesPresentationContext = true - navigationItem.searchController = searchController + // The searchTextField is now a subview of the controller's view, + // not part of the navigation bar's titleView. + // Delegate and target actions are set in setupViews(). } if options.enableLargeTitle { @@ -254,6 +282,7 @@ class PlacePickerViewController: UIViewController { // MARK: - UIViewController Lifecycle override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) if #available(iOS 13, *) { let appearance = UINavigationBarAppearance() // Configure with an opaque background to prevent transparency @@ -286,9 +315,10 @@ class PlacePickerViewController: UIViewController { } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - mapView.centerCoordinate = CLLocationCoordinate2D( - latitude: options.initialCoordinates.latitude, - longitude: options.initialCoordinates.longitude) + // Removed this line to prevent map recentering on layout changes (e.g., keyboard appearance) + // mapView.centerCoordinate = CLLocationCoordinate2D( + // latitude: options.initialCoordinates.latitude, + // longitude: options.initialCoordinates.longitude) mapView.delegate = self } override func viewSafeAreaInsetsDidChange() { @@ -301,6 +331,7 @@ class PlacePickerViewController: UIViewController { } } override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) if promise != nil && options.rejectOnCancel { promise?.reject("dismissed", "Modal closed by user") } @@ -308,6 +339,7 @@ class PlacePickerViewController: UIViewController { // MARK: - Navigation bar buttons methods @objc private func pickUserLocation() { + shouldCenterMapOnUserLocationUpdate = true // Set flag to true before requesting location locationManager.requestLocation() } @objc private func closePicker() { @@ -373,12 +405,11 @@ class PlacePickerViewController: UIViewController { self.lastLocation = nil // Ensure search bar placeholder text color is readable if #available(iOS 13.0, *) { - self.navigationItem.searchController?.searchBar.searchTextField - .attributedPlaceholder = NSAttributedString( - string: self.options.searchPlaceholder, - attributes: [.foregroundColor: UIColor.secondaryLabel]) + self.searchTextField.attributedPlaceholder = NSAttributedString( + string: self.options.searchPlaceholder, + attributes: [.foregroundColor: UIColor.secondaryLabel]) } else { - self.navigationItem.searchController?.searchBar.placeholder = + self.searchTextField.placeholder = self.options.searchPlaceholder } return @@ -386,21 +417,19 @@ class PlacePickerViewController: UIViewController { self.lastLocation = location?.first if let name = location?.first?.name { if #available(iOS 13.0, *) { - self.navigationItem.searchController?.searchBar.searchTextField - .attributedPlaceholder = NSAttributedString( - string: name, attributes: [.foregroundColor: UIColor.secondaryLabel] - ) + self.searchTextField.attributedPlaceholder = NSAttributedString( + string: name, attributes: [.foregroundColor: UIColor.secondaryLabel] + ) } else { - self.navigationItem.searchController?.searchBar.placeholder = name + self.searchTextField.placeholder = name } } else { if #available(iOS 13.0, *) { - self.navigationItem.searchController?.searchBar.searchTextField - .attributedPlaceholder = NSAttributedString( - string: self.options.searchPlaceholder, - attributes: [.foregroundColor: UIColor.secondaryLabel]) + self.searchTextField.attributedPlaceholder = NSAttributedString( + string: self.options.searchPlaceholder, + attributes: [.foregroundColor: UIColor.secondaryLabel]) } else { - self.navigationItem.searchController?.searchBar.placeholder = + self.searchTextField.placeholder = self.options.searchPlaceholder } } @@ -462,23 +491,20 @@ extension PlacePickerViewController: MKMapViewDelegate { } } -extension PlacePickerViewController: UISearchBarDelegate { - func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { - self.completerResults.removeAll() - return true - } - func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - // print("DID SEARCH") - } -} + +// Removed: extension PlacePickerViewController: UISearchBarDelegate + extension PlacePickerViewController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - if let coordinate = locations.first?.coordinate { + // Only center the map if explicitly requested by the user tapping the location button + if shouldCenterMapOnUserLocationUpdate, let coordinate = locations.first?.coordinate { mapView.setCenter(coordinate, animated: true) + shouldCenterMapOnUserLocationUpdate = false // Reset the flag after centering } } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print(error) + shouldCenterMapOnUserLocationUpdate = false // Reset flag on failure } } extension PlacePickerViewController: DropDownButtonDelegate { @@ -495,9 +521,10 @@ extension PlacePickerViewController: DropDownButtonDelegate { guard error == nil, let coords = result?.mapItems.first?.placemark.coordinate else { return } + // When a place is selected, explicitly center the map on that location self?.mapView.setCenter(coords, animated: true) - self?.searchController.searchBar.text = "" - self?.searchController.isActive = false + self?.searchTextField.text = "" + self?.searchTextField.resignFirstResponder() // Dismiss keyboard } } } @@ -512,12 +539,31 @@ extension PlacePickerViewController: MKLocalSearchCompleterDelegate { } } } -extension PlacePickerViewController: UISearchResultsUpdating { - func updateSearchResults(for searchController: UISearchController) { - if let searchText = searchController.searchBar.text, !searchText.isEmpty { + +// Removed: extension PlacePickerViewController: UISearchResultsUpdating + +extension PlacePickerViewController: UITextFieldDelegate { + @objc private func searchTextFieldDidChange(_ textField: UITextField) { + if let searchText = textField.text, !searchText.isEmpty { completer.queryFragment = searchText } else { completerResults.removeAll() } } + + func textFieldDidBeginEditing(_ textField: UITextField) { + searchResultContainer.isHidden = + completerResults.count < 1 && textField.text?.isEmpty ?? true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + searchResultContainer.isHidden = true + completerResults.removeAll() // Clear results when editing ends + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() // Dismiss keyboard on return + // You could also trigger a full search here if desired + return true + } } From 85492e8251136e3637b8da711aea609a7ac9af23 Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Sun, 5 Oct 2025 16:13:34 +0530 Subject: [PATCH 8/8] changes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03c47a5..28a3a8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-place-picker", - "version": "3.0.8", + "version": "3.0.9", "description": "Pick place with single click", "main": "build/index.js", "types": "build/index.d.ts",