refactor(UIKit): add search sheet to uikit

This commit is contained in:
Oliver
2025-05-16 14:07:51 +02:00
parent 546bdc4b2b
commit 8ec74c1012
6 changed files with 92 additions and 104 deletions

View File

@@ -15,14 +15,6 @@ struct ContentView: View {
@StateObject var locationManager = LocationManager() @StateObject var locationManager = LocationManager()
@StateObject var healthKitManager = HealthKitManager() @StateObject var healthKitManager = HealthKitManager()
@State private var position: MapCameraPosition = .automatic
@State private var showSearch: Bool = true
@State private var directions: [MKRoute] = []
@State private var destination: MKMapItem?
@State var healthKitAccess = false
@State var stepLength: Double?
// TODO: create a map // TODO: create a map
// Add navigation to the map // Add navigation to the map
@@ -42,60 +34,24 @@ struct ContentView: View {
// calculate only the distance between the start and end instead of getting directions for everything // calculate only the distance between the start and end instead of getting directions for everything
// if user clicks on the place, display better view and then calculate route there // if user clicks on the place, display better view and then calculate route there
var body: some View { var body: some View {
ZStack { MapView(locationManager: locationManager, viewModel: viewModel)
MapView(locationManager: locationManager) // Map(position: $position) {
// Map(position: $position) { // UserAnnotation()
// UserAnnotation() // ForEach(0..<directions.count) { i in
// ForEach(0..<directions.count) { i in // if destination != nil {
// if destination != nil { // Marker(item: destination!)
// Marker(item: destination!) // }
// } // MapPolyline(directions[i].polyline)
// MapPolyline(directions[i].polyline) // .stroke(Defaults.routeColor[i], lineWidth: Defaults.routeWidth)
// .stroke(Defaults.routeColor[i], lineWidth: Defaults.routeWidth) // }
// } .ignoresSafeArea()
} .onAppear {
.ignoresSafeArea() Task {
.sheet( await healthKitManager.requestAccess()
isPresented: $showSearch, viewModel.stepLength = await healthKitManager.getStepLength()
content: {
SearchView(
directions: $directions, stepLength: $stepLength,
locationManager: locationManager, destination: $destination
)
.ignoresSafeArea()
}
)
.mapControls {
// TODO: make sure the user location stays on the map even if camera moves
MapUserLocationButton()
.onTapGesture {
zoomUserLocation()
} }
MapCompass()
}
.onAppear {
zoomUserLocation()
Task {
await healthKitManager.requestAccess()
stepLength = await healthKitManager.getStepLength()
} }
}
.onChange(of: locationManager.location) {
zoomUserLocation()
}
} }
func zoomUserLocation() {
withAnimation {
locationManager.requestAuthorization()
locationManager.requestLocation()
if let userLocation = locationManager.location {
position = .camera(MapCamera(centerCoordinate: userLocation, distance: 1000))
}
}
}
func getLastPointFor(route: MKRoute) -> CLLocationCoordinate2D? { func getLastPointFor(route: MKRoute) -> CLLocationCoordinate2D? {
let pointCount = route.polyline.pointCount let pointCount = route.polyline.pointCount
if pointCount > 0 { if pointCount > 0 {

View File

@@ -13,20 +13,20 @@ import SwiftUI
struct SearchItemView: View { struct SearchItemView: View {
var location: MKMapItem var location: MKMapItem
@State var distance: CLLocationDistance? @State var distance: CLLocationDistance?
@Binding var directions: [MKRoute]
@Binding var stepLength: Double?
@Binding var showSteps: Bool @Binding var showSteps: Bool
@State var localDirections: [MKRoute] = [] @State var localDirections: [MKRoute] = []
@Binding var destination: MKMapItem? @ObservedObject var viewModel: ViewModel
var body: some View { var body: some View {
Button( Button(
action: { action: {
if localDirections == [] { if localDirections == [] {
print("finding directions")
findDirections() findDirections()
} }
directions = localDirections viewModel.directions = localDirections
print("Directions set")
}, },
label: { label: {
HStack { HStack {
@@ -86,7 +86,7 @@ struct SearchItemView: View {
} }
func formatDistance(distance: CLLocationDistance) -> String { func formatDistance(distance: CLLocationDistance) -> String {
let steps = distance / (stepLength ?? 1) let steps = distance / (viewModel.stepLength ?? 1)
if steps != 0 && showSteps { if steps != 0 && showSteps {
let formatter = NumberFormatter() let formatter = NumberFormatter()
formatter.maximumFractionDigits = 0 formatter.maximumFractionDigits = 0
@@ -118,7 +118,7 @@ struct SearchItemView: View {
} }
self.localDirections = response.routes self.localDirections = response.routes
self.distance = response.routes.first?.distance self.distance = response.routes.first?.distance
self.destination = location viewModel.destination = location
} }
} }
} }

View File

@@ -11,56 +11,54 @@ import SwiftUI
struct SearchView: View { struct SearchView: View {
@State private var query: String = "" @State private var query: String = ""
@State private var locations: [MKMapItem] = [] @State private var locations: [MKMapItem] = []
@Binding var directions: [MKRoute]
@Binding var stepLength: Double?
@State var showSteps = true @State var showSteps = true
var locationManager: LocationManager var locationManager: LocationManager
@Binding var destination: MKMapItem? @ObservedObject var viewModel: ViewModel
var body: some View { var body: some View {
VStack { ZStack {
HStack { Rectangle()
Image(systemName: "magnifyingglass") .fill(.thinMaterial)
TextField("Search for any location", text: $query) .ignoresSafeArea()
.autocorrectionDisabled() VStack {
.onChange(of: self.query) { HStack {
if query.count > 0 { Image(systemName: "magnifyingglass")
search(for: self.query) TextField("Search for any location", text: $query)
} else { .autocorrectionDisabled()
self.locations = [] .onChange(of: self.query) {
if query.count > 0 {
search(for: self.query)
} else {
self.locations = []
}
} }
}
// .onAppear { // .onAppear {
// // TODO: delete this, it's for debug only // // TODO: delete this, it's for debug only
// search(for: self.query) // search(for: self.query)
// } // }
.overlay { .overlay {
HStack { HStack {
Spacer() Spacer()
Image(systemName: "multiply.circle.fill") Image(systemName: "multiply.circle.fill")
.foregroundStyle(.gray) .foregroundStyle(.gray)
.onTapGesture { .onTapGesture {
query = "" query = ""
} }
}
} }
}
.modifier(TextFieldGrayBackgroudColor())
Spacer()
ScrollView {
ForEach(self.locations, id: \.identifier) { location in
SearchItemView(location: location, showSteps: $showSteps, viewModel: viewModel)
} }
}
.modifier(TextFieldGrayBackgroudColor())
Spacer()
ScrollView {
ForEach(self.locations, id: \.identifier) { location in
SearchItemView(
location: location, directions: $directions, stepLength: $stepLength,
showSteps: $showSteps, destination: $destination)
} }
} }
.padding()
.interactiveDismissDisabled()
.ignoresSafeArea()
} }
.padding()
.interactiveDismissDisabled()
.presentationDetents([.height(200), .large])
.presentationBackground(.regularMaterial)
.presentationBackgroundInteraction(.enabled(upThrough: .large))
} }
func search(for text: String) { func search(for text: String) {

View File

@@ -11,6 +11,9 @@ import SwiftUI
class UIKitMapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate { class UIKitMapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var locationManager: LocationManager var locationManager: LocationManager
var viewModel: ViewModel
var directions: [MKRoute] = []
var destination: MKMapItem?
let mapView : MKMapView = { let mapView : MKMapView = {
let map = MKMapView() let map = MKMapView()
map.showsUserTrackingButton = true map.showsUserTrackingButton = true
@@ -18,8 +21,10 @@ class UIKitMapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelega
return map return map
}() }()
init(locationManager: LocationManager) {
init(locationManager: LocationManager, viewModel: ViewModel) {
self.locationManager = locationManager self.locationManager = locationManager
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
} }
@@ -35,6 +40,10 @@ class UIKitMapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelega
setLocation() setLocation()
} }
override func viewDidAppear(_ animated: Bool) {
setSearchView()
}
private func setLocation() { private func setLocation() {
locationManager.requestAuthorization() locationManager.requestAuthorization()
locationManager.requestLocation() locationManager.requestLocation()
@@ -54,14 +63,35 @@ class UIKitMapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelega
mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true mapView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
} }
private func setSearchView() {
let searchViewConctroller = UIHostingController(rootView: SearchView(locationManager: locationManager, viewModel: viewModel))
searchViewConctroller.view.backgroundColor = .clear
searchViewConctroller.modalPresentationStyle = .pageSheet
searchViewConctroller.edgesForExtendedLayout = [.top, .bottom, .left, .right]
if let sheet = searchViewConctroller.sheetPresentationController {
let smallDetentId = UISheetPresentationController.Detent.Identifier("small")
let smallDetent = UISheetPresentationController.Detent.custom(identifier: smallDetentId) { context in
return 200
}
sheet.detents = [smallDetent, .large()]
sheet.largestUndimmedDetentIdentifier = .large
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersGrabberVisible = true
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
self.present(searchViewConctroller, animated: true, completion: nil)
}
} }
struct MapView: UIViewControllerRepresentable { struct MapView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIKitMapView typealias UIViewControllerType = UIKitMapView
@StateObject var locationManager: LocationManager @StateObject var locationManager: LocationManager
@ObservedObject var viewModel: ViewModel
func makeUIViewController(context: Context) -> UIKitMapView { func makeUIViewController(context: Context) -> UIKitMapView {
return UIKitMapView(locationManager: locationManager) return UIKitMapView(locationManager: locationManager, viewModel: viewModel)
} }
func updateUIViewController(_ uiViewController: UIKitMapView, context: Context) { func updateUIViewController(_ uiViewController: UIKitMapView, context: Context) {

View File

@@ -6,9 +6,13 @@
// //
import Foundation import Foundation
import MapKit
class ViewModel: ObservableObject { class ViewModel: ObservableObject {
@Published var test: String = UserDefaults.standard.string(forKey: "test") ?? "" @Published var test: String = UserDefaults.standard.string(forKey: "test") ?? ""
@Published var directions: [MKRoute] = []
@Published var stepLength: Double?
@Published var destination: MKMapItem?
func saveValue(_ value: String) { func saveValue(_ value: String) {
UserDefaults.standard.set(value, forKey: "test") UserDefaults.standard.set(value, forKey: "test")