refactor(UIKit): add search sheet to uikit
This commit is contained in:
Binary file not shown.
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user