SwiftUI Showing Specific Landmarks on Opening the map - swift

I was wondering to see if there is a way to show the nearest specific landmarks[ie railway stations] just to see where the user can go to? I am trying to also show the point to point of landmarks but should revolve first around the user. top 3 nearest landmarks should do, please any help or resources?
import MapKit
import SwiftUI
import CoreLocation
struct SearchView: View {
var body: some View {
Home()
}
}
struct SearchView_Previews: PreviewProvider {
static var previews: some View {
SearchView()
}
}
struct Home: View {
#State var map = MKMapView()
#State var manager = CLLocationManager()
#State var alert = false
#State var source : CLLocationCoordinate2D!
#State var destination : CLLocationCoordinate2D!
var body: some View{
ZStack(alignment: .bottom){
VStack(spacing:0){
HStack{
Text("Station Search").font(.title)
Spacer()
}
.padding()
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top).background(Color.white)
MapView(map: self.$map, manager: self.$manager, alert:
self.$alert, source: self.$source,
destination: self.$destination)
.onAppear {
self.manager.requestAlwaysAuthorization()
}
}
}.edgesIgnoringSafeArea(.all)
.alert(isPresented: self.$alert) { () -> Alert in
Alert(title: Text("Error"), message: Text("Please Enable Location In Setting !!!"), dismissButton: .destructive(Text("Ok")))
}
}
}
struct MapView: UIViewRepresentable {
#Binding var map: MKMapView
#Binding var manager : CLLocationManager
#Binding var alert : Bool
#Binding var source : CLLocationCoordinate2D!
#Binding var destination : CLLocationCoordinate2D!
func makeCoordinator() -> Coordinator {
return MapView.Coordinator(parent1: self)
}
func makeUIView(context: Context) -> MKMapView {
map.delegate = context.coordinator
manager.delegate = context.coordinator
map.showsUserLocation = true
return map
}
func updateUIView(_ uiView: MKMapView, context: Context){
}
class Coordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
var parent : MapView
init(parent1 : MapView) {
parent = parent1
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .denied {
self.parent.alert.toggle()
}
else{
self.parent.manager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) {
let region = MKCoordinateRegion(center: locations.last!.coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
self.parent.source = locations.last!.coordinate
self.parent.map.region = region
}
}
}

Related

Can I control Reality Composer behaviors in RealityKit?

I would like to make a button using SwiftUI. When the button is pressed, the model will hide. I have already read the tutorial in this link (Creating a Trigger), but I don't know how to control it programmatically.
Here is my code:
struct VocabView : View {
#State private var arView = ARView(frame: .zero)
var body: some View {
ZStack{
ARViewContainer(arView: $arView)
.ignoresSafeArea()
VStack {
Button("hide") {
hide()
}
}
}
}
func hide() {
let demoScene = try! Experience1.loadDemo()
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
demoScene.notifications.hide.post()
}
}
}
}
struct ARViewContainer2: UIViewRepresentable {
#Binding var arView: ARView
func makeUIView(context: Context) -> ARView {
let demoScene = try! Experience1.loadDemo()
DispatchQueue.main.async {
arView.scene.anchors.append(demoScene)
}
return arView
}
}
Here is the configuration in Reality Composer:
You are loading your model twice – at first in makeUIView() method and secondly in hide() method. Try my version.
import SwiftUI
import RealityKit
struct ContentView : View {
#State private var arView = ARView(frame: .zero)
#State private var scene = try! Experience.loadBox()
var body: some View {
ZStack{
ARViewContainer(arView: $arView, scene: $scene)
.ignoresSafeArea()
VStack {
Spacer()
Button("Hide Model") { hideModel() }
}
}
}
private func hideModel() {
if arView.scene.anchors.count > 0 {
if arView.scene.anchors[0].isAnchored {
scene.notifications.notify.post()
}
}
}
}
struct ARViewContainer : UIViewRepresentable {
#Binding var arView: ARView
#Binding var scene: Experience.Box
func makeUIView(context: Context) -> ARView {
arView.scene.anchors.append(scene)
return arView
}
func updateUIView(_ view: ARView, context: Context) { }
}

Method `enumerateChildNodes` is not finding nodes

I'm developing an ARKit app that has 2 buttons: "Play" and "Reset". When I click on Play, a pyramid node is displayed. When I click on Reset, all pyramid nodes should be deleted from the scene:
Another requirement is that I want to disable the Play button when I click on it.
Next is my contentView code:
import ARKit
struct ContentView: View {
#State var node = SCNNode()
#State var play: Bool = false
#State var reset: Bool = false
#State var isDisabled: Bool = false
#State var showBanner:Bool = true
var body: some View {
ZStack {
SceneKitView(node: $node, play: $play, reset: $reset, isDisabled: $isDisabled)
VStack {
Spacer()
HStack {
Button(action: {
play = true
}) {
Image("Play")
}.padding()
.disabled(isDisabled) /// <<<<< THIS DISABLES THE BUTTON
Spacer()
Button(action: {
play = false
reset = true
}) {
Image("Reset")
}.padding()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In order to disable the Play button, I'm using a .disabled and the Boolean isDisabled.
My code changes the value of isDisabled to TRUE in a Coordinator :
import ARKit
import SwiftUI
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
let config = ARWorldTrackingConfiguration()
#Binding var node: SCNNode
#Binding var play: Bool
#Binding var reset: Bool
#Binding var isDisabled: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, ARSCNViewDelegate {
var control: SceneKitView
init(_ control: SceneKitView) {
self.control = control
}
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
DispatchQueue.main.async {
if self.control.play {
self.control.addNode()
self.control.play = false
self.control.isDisabled = true // HERE I UPDATE THE VALUE !!!!!!!!
self.control.reset = false
}else{
}
}
}
}
func removeNode(){
self.arView.scene.rootNode.enumerateChildNodes { (node, _) in
print("existing nodes = \(node)")
node.removeFromParentNode()
}
}
func updateUIView(_ uiView: ARSCNView,
context: Context) {
if self.reset {
self.removeNode()
print("game reseted")
}
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.delegate = context.coordinator
arView.autoenablesDefaultLighting = true
//arView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
arView.showsStatistics = true
arView.session.run(self.config)
return arView
}
func addNode(){
let pyramid = SCNNode(geometry: SCNPyramid(width: 0.1, height: 0.1, length: 0.1))
pyramid.geometry?.firstMaterial?.diffuse.contents = UIColor.red
pyramid.geometry?.firstMaterial?.specular.contents = UIColor.white
pyramid.position = SCNVector3(0,0,-0.5)
self.arView.scene.rootNode.addChildNode(pyramid)
}
}
Problem: When the app is running and I click on RESET, the method removeNode is invoked and this method uses enumerateChildNodes to find the nodes and delete them using removeFromParentNode but the pyramid node is not removed ! :(
The crazy thing (for me) is that if I don't change the value of isDisabled in the Coordinator, i.e. I comment that line, removeNode works, and the node is removed !!!!!
Any comment, suggestion is welcome :)
This solves the issue:
func updateUIView(_ uiView: ARSCNView, context: Context) {
if self.reset {
DispatchQueue.main.async {
context.coordinator.control.removeNode()
context.coordinator.control.isDisabled = false
}
print("Game Reset")
}
}

How to get access to underlying GMSMapView from UIRepresentable MapView

In my app I have a ViewModel(MapViewModel) class, a UIRepresentable class and ContentView. I am looking for a way to get access to GMSMapView view in the ViewModel that is created as a UIRepresentable class.
ContentView.swift:
import SwiftUI
import Combine
struct ContentView: View {
#State private var selection = 0
#State private var dragOffset = CGSize.zero
#ObservedObject var mapViewModel : MapViewModel = MapViewModel()
var body: some View {
GeometryReader { geo in
TabView(selection: self.$selection) {
MapView()
.edgesIgnoringSafeArea(.all)
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
.tag(0)
Text("Second Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Settings")
}
}
.tag(1)
Text("Third Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Third Page")
}
}
.tag(2)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
MapViewModel.swift:
import Foundation
import Combine
import GoogleMaps
import os
class MapViewModel: NSObject, ObservableObject {
let lm = CLLocationManager()
var myLocations = [CLLocation]()
override init() {
super.init()
lm.delegate = self
lm.desiredAccuracy = kCLLocationAccuracyBest
lm.requestWhenInUseAuthorization()
lm.pausesLocationUpdatesAutomatically = false
lm.allowsBackgroundLocationUpdates = true
lm.startUpdatingLocation()
}
}
extension MapViewModel: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
//self.status = status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
os_log("locationManager:didUpdateLocations: received location",log: Log.general, type: .debug)
}
}
MapView.swift:
import UIKit
import SwiftUI
import GoogleMaps
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: 30.267153, longitude: -97.7430608, zoom: 6.0)
let gmsMapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
gmsMapView.delegate = context.coordinator
return gmsMapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, GMSMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
}
struct GoogleMapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
So any idea on how I can access the gmsMapView object in MapViewModel. I need access to draw lines on the map...Thanks!
I was able to get past this issue by defining the polyline overly value as #Published and accessing it via viewmodel in updateUI. So instead of trying to access the mapview from the viewmodel, I let the view add the polyline overlay. Hope this helps someone else. Thanks
ContentView.swift:
struct ContentView: View {
#State private var selection = 0
#State private var dragOffset = CGSize.zero
#ObservedObject var mapViewModel: MapViewModel
var body: some View {
GeometryReader { geo in
TabView(selection: self.$selection) {
MapView()
.edgesIgnoringSafeArea(.all)
.tabItem {
VStack {
Image(systemName: "house")
Text("Home")
}
}
.tag(0)
Text("Second Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Settings")
}
}
.tag(1)
Text("Third Page")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Third Page")
}
}
.tag(2)
}
}
}
}
MapView:
struct MapView: UIViewRepresentable {
#ObservedObject var mapViewModel = MapViewModel()
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: 30.5986015, longitude: -97.8210401, zoom: 20.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.isMyLocationEnabled = true
mapView.animate(toViewingAngle: 45)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
if (mapViewModel.polyline != nil) {
print("updateUIView: Polyline = \(mapViewModel.polyline!.description)")
os_log("updateUIView: Polyline = %{Public}s",log: Log.general, type: .debug, mapViewModel.polyline!.description)
mapViewModel.polyline!.strokeColor = UIColor.red
mapViewModel.polyline!.strokeWidth = 5.0
mapViewModel.polyline!.map = mapView
}
if (mapViewModel.locChanged && mapViewModel.myLocations.count > 0) {
print("updateUIView: Refocus camera on last location")
os_log("updateUIView: Refocus camera on last location",log: Log.general, type: .debug, mapViewModel.polyline!.description)
let camera = GMSCameraPosition.camera(withLatitude: (mapViewModel.myLocations.last?.coordinate.latitude)!, longitude: (mapViewModel.myLocations.last?.coordinate.longitude)!, zoom: 20.0)
let _ = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, GMSMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
}
MapViewModel:
class MapViewModel: NSObject, ObservableObject {
.
.
.
#Published var polyline: GMSPolyline?
#Published var locChanged: Bool = false
override init() {
super.init()
.
.
.
polyline = nil
}
.
.
.
func draw(myLocations: [CLLocation], color: UIColor) {
os_log("MapViewController: Drawing Track for last two Locations",log: Log.general, type: .info)
print("MapViewController: Drawing Track for last two Locations")
let path = GMSMutablePath()
let c1 = myLocations[myLocations.count - 1].coordinate
let c2 = myLocations[myLocations.count - 2].coordinate
path.add(c1)
path.add(c2)
polyline = GMSPolyline(path: path)
print("draw: Polyline = \(polyline!.description)")
polyline!.strokeColor = color
polyline!.strokeWidth = 5.0
}
.
.
.
extension MapViewModel: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
os_log("locationManager:didUpdateLocations: received location",log: Log.general, type: .debug)
guard let lastLocation: CLLocation = locations.last else {
os_log("locationManager:didUpdateLocations: received null location",log: Log.general, type: .debug)
return
}
if (myLocations.count >= 2) {
os_log("MapViewController: updateLocation: Calling draw method with count = %{Public}s",log: Log.general, type: .debug, myLocations.count.description)
self.draw(myLocations: self.myLocations, color:.red)
}
.
.
.
} else {
os_log("LocationManager: Bad Location...",log: Log.general, type: .error)
badLocationCount += 1
locChanged = false
}
}
}

Handle SwiftUI and CoreLocation with MVVM-Pattern

i am trying to implement SwiftUI and CoreLocation with the MVVM-Pattern. My LocationManager as Helper works fine. But how I can change the properties of my LocationViewModel? I am implemented my #ObservedObject of the LocationManager in LocationViewModel. Here is my problem.
I don't have a idea to implement properties they change on the fly. Nothing is changed in my LocationView. By pressing a Button anything works fine one time. But the LocationViewModel must change there properties on every change of the LocationManager.
In summary I would like to display the current user position.
// Location Manager as Helper
import Foundation
import CoreLocation
class LocationManager: NSObject, ObservableObject {
let locationManager = CLLocationManager()
let geoCoder = CLGeocoder()
#Published var location: CLLocation?
#Published var placemark: CLPlacemark?
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
func geoCode(with location: CLLocation) {
geoCoder.reverseGeocodeLocation(location) { (placemark, error) in
if error != nil {
print(error!.localizedDescription)
} else {
self.placemark = placemark?.first
}
}
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
DispatchQueue.main.async {
self.location = location
self.geoCode(with: location)
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// TODO
}
}
// Location Model
import Foundation
import CoreLocation
struct Location {
var location: CLLocation = CLLocation()
var placemark: CLPlacemark = CLPlacemark()
}
// Location View Model
import SwiftUI
import CoreLocation
class LocationViewModel: ObservableObject {
#ObservedObject var locationManager: LocationManager = LocationManager()
#Published var location: Location
init() {
self.location = Location()
}
}
// Location View
import SwiftUI
struct LocationView: View {
#ObservedObject var locationViewModel: LocationViewModel = LocationViewModel()
var body: some View {
VStack(alignment: .leading) {
Text("Latitude: \(self.locationViewModel.location.location.coordinate.latitude.description)")
Text("Longitude: \(self.locationViewModel.location.location.coordinate.longitude.description)")
}
}
}
struct LocationView_Previews: PreviewProvider {
static var previews: some View {
LocationView()
}
}
Update
Now, I have set up my MapView.
But how I can receive the data of my LocationManager? The didUpdateLocations method is working in LocationManager.
All what I am trying to do goes wrong. I would like to set the region on my MapView based on the current user location. In UIKit it was very simple, but in SwiftUI it is freaky.
// Map View
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
#ObservedObject var locationManager: LocationManager = LocationManager()
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ control: MapView) {
self.parent = control
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView(frame: .zero)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.showsUserLocation = true
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
SwiftUI 2
Use instead StateObject in this case
struct LocationView: View {
#StateObject var locationManager: LocationManager = LocationManager()
...
SwiftUI 1
Actually LocationViewModel is redundant here. As your LocationManager is a ObservableObject you can use it directly in your view, as below:
struct LocationView: View {
#ObservedObject var locationManager: LocationManager = LocationManager()
var body: some View {
VStack(alignment: .leading) {
Text("Latitude: \(locationManager.location.coordinate.latitude.description)")
Text("Longitude: \(locationManager.location.coordinate.longitude.description)")
}
}
}

SwfitUI navigationBarItems calls location permission twice

I created a button under navigationBarItems, and this button opens a new sheet, and the new sheet will pop up a window to ask user locations. However, in the new sheet, CLLocationManager() has been called twice, and location permission pop up window will disappear in a few seconds. When you create a regular button, the location pop up windown will be stay there until you select one of the options, and CLLocationManager() will only be called one time.
Code
ConentView.swift
import SwiftUI
struct ContentView: View {
#State var show = false
#State var showEditPage = false
var body: some View {
NavigationView {
List {
Text("Text")
Button("Location button") {
print("Location button tapped")
self.show.toggle()
}.sheet(isPresented: $show) {
NewPage()
}
}
.navigationBarItems(
trailing:
VStack {
Button(action: {
print("BarItemButton tapped")
self.showEditPage.toggle()
}) {
//Top right icon
Text("BarItemButton")
}.sheet(isPresented: $showEditPage) {
//Open sheet page
NewPage()
}
}//End of trailing VStack
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
NewPage.swift
import SwiftUI
struct NewPage: View {
#ObservedObject var locationManager = LocationManager()
var body: some View {
Text("New Page")
}
}
struct NewPage_Previews: PreviewProvider {
static var previews: some View {
NewPage()
}
}
LocationManager.swift
import SwiftUI
import Foundation
import CoreLocation
import Combine
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
let objectWillChange = PassthroughSubject<Void, Never>()
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
print("In LocationManger.swift #initi, this is called")
}
#Published var locationStatus: CLAuthorizationStatus? {
willSet {
objectWillChange.send()
}
}
#Published var lastLocation: CLLocation? {
willSet { objectWillChange.send() }
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.locationStatus = status
print("In LocationManger.swift #Func locationManager, Status is updaing")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.lastLocation = location
print("Location is updating")
}
}
GitHub
You can feel free to download the project to try it on your laptop to see the issue:
Github Example Project
Screenshot
Here are changes of possible approach to make it work in your code:
1) Make LocationManager be only one
class LocationManager: NSObject, ObservableObject {
static var defaultManager: LocationManager = {
LocationManager()
}()
...
2) Use default manager instead of creating every time SwiftUI wants to create/copy view structure
struct NewPage: View {
#ObservedObject var locationManager = LocationManager.defaultManager
...