Use of unresolved identifier 'Map' SwiftUI - ios

I am following the official tutorial for SwiftUI and have run into the error message 'Use of unresolved identifier 'Map'. Even when I copy and and paste the code from the tutorial, it is still giving me the error. I have looked at a few solutions for similar issues and can't seem to find anything that will work. Code below.
import SwiftUI
import MapKit
struct MapView: View {
#State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView()
}
}
I apologise if this is really obvious - but I'm new to Swift/SwiftUI, and can't see where the issue is coming from. Thanks in advance!

I also got into trouble in this case.
So, I make View method using UIViewRepresentable with 2 methods: 'makeUIView' and 'updateUIView'.
MapView
import SwiftUI
import MapKit
struct MapViewUI: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: 2.6540427, longitude: 98.8932576)
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
And, You can call your View looks like this
struct MapPageView: View {
var body: some View {
VStack {
MapViewUI()
.frame(height: 300)
}
}
}

Problem is related to import MapKit. Because Map is defined in MapKit. Please verify if you are able to import MapKit properly

Related

Map view crashing on orientation change

Trying to fix a crash I'm getting in the simulator when using MapKit to display a map. It only occurs when switching between landscape and portrait. I've simplified the code while still replicating the crash to try to isolate the problem. Looks like this:
struct ContentView: View {
#State var region: MKCoordinateRegion
init() {
_region = .init(initialValue: MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 0, longitude: 0), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)))
}
var body: some View {
MapPreview(region: $region)
}
}
struct MapPreview: View {
#Binding var region: MKCoordinateRegion
var body: some View {
Map(coordinateRegion: $region,
interactionModes: []
)
.edgesIgnoringSafeArea(.horizontal)
.edgesIgnoringSafeArea(.bottom)
}
}
Crash message isn't much help, just gives me EXC_BAD_ACCESS (code=EXC_I386_GPFLT). Thanks for any help!

Is it possible to UI Test individual Swift UI components?

I am super new to Swift and SwiftUI and I have started a new project using SwiftUI. I have some experience in other component based libraries for the web and I wanted a way to use the same pattern for iOS development.
Is there a way to ui test individual components in SwiftUI? For example, I have created a Map component that accepts coordinates and renders a map and I want to test this map individually by making the app immediately render the component. Here is my code and test code at the moment:
// App.swift (main)
// Map is not rendered yet
#main
struct PicksApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// MyMap.swift
struct MyMap: View {
#State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: 25.7617,
longitude: 80.1918
),
span: MKCoordinateSpan(
latitudeDelta: 10,
longitudeDelta: 10
)
)
var body: some View {
Map(coordinateRegion: $region)
}
}
struct MyMap_Previews: PreviewProvider {
static var previews: some View {
MyMap()
}
}
// MyMapUITests.swift
class MyMapUITests: XCTestCase {
func testMapExists() throws {
let app = XCUIApplication()
app.launch()
let map = app.maps.element
XCTAssert(map.exists, "Map does not exist")
}
}
Is it possible to tell UI Test framework to only test one component instead of launching the entire app and making me navigate between each view before I am able to get to my view?
For example, in my case, there is going to be a login view when the app opens for the first time (which is every time from perspective of ui testing) and the map view can be located inside the app somewhere. I want to be able to test only the map view without testing end-to-end user experience.
One approach you could take is to transform your app into a catalog one if some environment variables are found. For this you'll have to keep a fixed collection of views to use as the root of the app:
#main
struct PicksApp: App {
static let viewBuilders: [String: () -> AnyView] = [
"ContentView": { AnyView(ContentView()) },
"MyMap": { AnyView(MyMap()) }]
var body: some Scene {
WindowGroup {
if let viewName = ProcessInfo().customUITestedView,
let viewBuilder = Self.viewBuilders[viewName] {
viewBuilder()
} else {
AnyView(ContentView())
}
}
}
}
Here's the ProcessInfo helper method:
extension ProcessInfo {
var customUITestedView: String? {
guard environment["MyUITestsCustomView"] == "true" else { return nil }
return environment["MyCustomViewName"]
}
}
With the above changes, the UI test needs only two more lines of code - the enviroment preparation:
func testMapExists() throws {
let app = XCUIApplication()
app.launchEnvironment["MyUITestsCustomView"] = "true"
app.launchEnvironment["MyCustomViewName"] = "MyMap"
app.launch()
let map = app.maps.element
XCTAssert(map.exists, "Map does not exist")
}

Subscribe to MQTT topic in SwiftUI iOS application

I'm writing a small iOS app to send MQTT messages to a broker which is hosted on a Raspberry Pi companion computer on my quadcopter. In the iOS app primary view, the app will connect to MQTT Broker (check), send messages associated with several buttons (check), and monitor various quadcopter telemetries such as mode, distance, and lat/long. The iPhone app will display the drones position and the user's position (check) on a map.
The part I'm having issues with currently is having the app maintain a continuous subscription to a topic and then update several variables in the code. Currently, the code below contains the subscription line and "did receive message" code inside of of a button view which only works momentarily when that button is pressed.
I've tried pasting this code in various places within the content view with no success; admittedly I'm a novice coder and Swift/iOS is very new to me. Ideally, the app would continually monitor certain topics and update several variables to equal the message when certain topics are posted to by the drone.
import MapKit
import SwiftUI
import CocoaMQTT
struct Drone {
var coordinate: CLLocationCoordinate2D
var distance: UInt8
var direction: UInt8
}
//
struct ContentView: View {
#State public var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 34.0000, longitude: -86.0000), span: MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001))
#State private var trackingMode = MapUserTrackingMode.follow
#State private var safety = false
#State private var showingSheet = false
#State public var Connection:Bool = false
var Bee: Drone = Drone(coordinate: .init(latitude: 34.0010, longitude: -86.0010), distance: 10, direction: 180)
let mqttClient = CocoaMQTT(clientID: "swift", host: "mqtt.eclipse.org", port: 1883)
var body: some View {
VStack {
HStack{
Text("Beer Bee")
Spacer()
Button(action: {
self.mqttClient.username="user"
self.mqttClient.password="password"
self.mqttClient.keepAlive=60
self.mqttClient.connect()
self.Connection.toggle()
}, label: {
Text(Connection ? "Disconnect":"Connect")
})
}
Map(coordinateRegion: $region, interactionModes: .zoom, showsUserLocation: true, userTrackingMode: $trackingMode)
HStack {
Text("Flight Mode")
Spacer()
Text("Distance")
Spacer()
Text("Position Accuracy")
}
HStack(alignment: .center) {
Toggle(isOn: $safety) {
//action here
}
.toggleStyle(SwitchToggleStyle(tint: Color.red))
Button("Main") {
self.mqttClient.publish("topic/dronetest", withString: "hello world!")
}
Spacer()
}
Button("Hold") {
self.mqttClient.subscribe("topic/dronetest")
self.mqttClient.didReceiveMessage = { mqtt, message, id in
print("Message received in topic \(message.topic) with payload \(message.string!)")
}
}
Spacer()
Button("Manual Control") {
self.showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SecondView()//region: self.region, Connection: self.Connection)
}
Spacer()
}
.background(/*#START_MENU_TOKEN#*//*#PLACEHOLDER=View#*/Color.yellow/*#END_MENU_TOKEN#*/)
}
}
this is my first answer in StackOverflow and I'm a novice too. I was having the same problem, but I can solve it by declaring the "didConnectAck" for the mqttClient; the subscription remains. Just add the following missing lines to your code:
self.mqttClient.didConnectAck = { mqtt, ack in
self.mqttClient.subscribe("topic/dronetest")
self.mqttClient.didReceiveMessage = { mqtt, message, id in
print("Message received in topic \(message.topic) with payload \(message.string!)")
}
}
Also, (maybe?) can be useful to add the following line in your first button:
self.mqttClient.autoReconnect = true
I hope I can help you with this answer. Good luck.

How to style Google Maps in SwiftUI with JSON

I am new to XCode 11 and SwiftUI and have been following a tutorial (famous last words): https://developers.google.com/maps/documentation/ios-sdk/styling, trying to create a custom Google Maps interface using the Google Maps SDK. I have a basic map view in my app already, but I am trying to use a JSON file to style the map (specifically, I am trying to highlight local roads). My problem is that the solution provided by the Google documentation is not for SwiftUI, as it uses a ViewController. How should I use a JSON file to style the maps in SwiftUI? As I said I am a total newbie to Swift, so I would greatly appreciate answers on a beginner level, if possible. My code for the Map View called in ContentView.swift looks like this (not sure if it's relevant):
import SwiftUI
import GoogleMaps
struct GoogleMapsView: UIViewRepresentable {
private let zoom: Float = 15.0
func makeUIView(context: Self.Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: 33.3969, longitude: -84.5963, zoom: 13.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Context) {
}
}
struct GoogleMapsView_Previews: PreviewProvider {
static var previews: some View {
GoogleMapsView()
}
}
I think it doesn't matter if you're using SwiftUI or not.
The important part is:
if let styleURL = Bundle.main.url(forResource: "style", withExtension: "json") {
mapView.mapStyle = try GMSMapStyle(contentsOfFileURL: styleURL)
just add it between this:
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
->
return mapView

Cannot get #EnvironmentObject in Controller

I'm trying to use #EnvironmentObject to control some aspects of my app. The issue I'm having is that one of my controllers can't access the environment object. I get the fatal error "No #ObservableObject of type Environment found".
I've searched other questions, and every solution I could find consisted of sending .environmentObject(myEnvironment) to the view in question. The problem is this is not a view, and I don't seem to have that option.
Also, in my SceneDelegate I send the environmentObject to the first view, so that is not the problem.
Here is my code.
First, I created a model to declare all my environment variables
Environment
struct Environment {
var showMenu: Bool
var searchText: String
var location : Location
init() {
self.showMenu = false
self.searchText = ""
self.location = Location()
}
}
Next I have a controller which purpose is to handle any actions related to the environment, right now it has none
EnvironmentController
import Foundation
class EnvironmentController : ObservableObject {
#Published var environment = Environment()
}
Now, in the SceneDelegate I call the NextDeparturesView, which in turn calls, the MapView.
MapView
import SwiftUI
import MapKit
//MARK: Map View
struct MapView : UIViewRepresentable {
#EnvironmentObject var environmentController: EnvironmentController
var locationController = LocationController()
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ uiView: MKMapView, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: environmentController.environment.location.latitude,
longitude: environmentController.environment.location.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: coordinate, span: span)
uiView.showsUserLocation = true
uiView.setRegion(region, animated: true)
}
}
You'll notice that in the MapView I call the LocationController, which is where the fatal error occurs
LocationController
import SwiftUI
import MapKit
import CoreLocation
final class LocationController: NSObject, CLLocationManagerDelegate, ObservableObject {
//MARK: Vars
#EnvironmentObject var environmentController: EnvironmentController
#ObservedObject var userSettingsController = UserSettingsController()
//var declaration - Irrelevant code to the question
//MARK: Location Manager
var locationManager = CLLocationManager()
//MARK: Init
override init() {
//more irrelevant code
super.init()
//Ask for location access
self.updateLocation()
}
//MARK: Functions
func updateLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
if locationManager.responds(to: #selector(CLLocationManager.requestAlwaysAuthorization)){
locationManager.requestAlwaysAuthorization()
}
else {
locationManager.startUpdatingLocation()
}
}
//MARK: CLLocationManagerDelegate methods
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error updating location :%#", error)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
self.setDefaultLocation()
break
case .restricted:
self.setDefaultLocation()
break
case .denied:
self.setDefaultLocation()
break
default:
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let currentLocation = manager.location?.coordinate
self.environmentController.environment.location.latitude = Double(currentLocation!.latitude)
self.environmentController.environment.location.longitude = Double(currentLocation!.longitude)
manager.stopUpdatingLocation()
}
//MARK: Other Functions
func recenter() {
locationManager.startUpdatingLocation()
}
func setDefaultLocation() {
if self.$userSettingsController.userCity.wrappedValue == "" {
self.environmentController.environment.location.latitude = 0.0
self.environmentController.environment.location.longitude = 0.0
} else {
self.environmentController.environment.location.latitude = self.citiesDictionary[self.userSettingsController.userCity]!.latitude
self.environmentController.environment.location.longitude = self.citiesDictionary[self.userSettingsController.userCity]!.longitude
}
}
}
So, this is where the fatal error occurs. For instance, my app usually calls setDefaultLocation() first, and the app is crashing there. Any idea what I am doing wrong, or how to solve it?
Thank you in advance.
EDIT
After much help from #pawello2222 I've solved my problem, however with some changes to the overall structure of my application.
I will accept his answer as the correct one, but I'll provide a list of things that I did, so anyone seeing this in the future might get nudged in the right direction.
I was wrongly assuming that View and UIViewRepresentable could both access the #EnvironmentObject. Only View can.
In my Environment struct, instead of a Location var, I now have a LocationController, so the same instance is used throughout the application. In my LocationController I now have a #Published var location: Location, so every View has access to the same location.
In structs of the type View I create the #EnvironmentObject var environmentController: EnvironmentController and use the LocationController associated with it. In other class types, I simply have an init method which receives a LocationController, which is sent through the environmentController, for instance, when I call MapView I do: MapView(locController: environmentController.environment.locationController) thus insuring that it is the same controller used throughout the application and the same Location that is being changed. It is important that to use #ObservedObject var locationController: LocationController in classes such as MapView, otherwise changes won't be detected.
Hope this helps.
Don't use #EnvironmentObject in your Controller/ViewModel (in fact anywhere outside a View). If you want to observe changes to Environment in your Controller you can do this:
class Environment: ObservableObject {
#Published var showMenu: Bool = false
#Published var searchText: String = ""
#Published var location : Location = Location()
}
class Controller: ObservableObject {
#Published var showMenu: Bool
private var environment: Environment
private var cancellables = Set<AnyCancellable>()
init(environment: Environment) {
_showMenu = .init(initialValue: environment.showMenu)
environment.$showMenu
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] value in
self?.showMenu = value
})
.store(in: &cancellables)
}
}
You can also use other forms of Dependency Injection to inject the Environment (or even use a singleton).
Generally there are different ways to show your Environment variables (eg. showMenu) in the View (and refresh it):
1) The Environment is injected into your View (NOT to ViewModel) as an #EnvironmentObject - for cases when you need to access the Environment from the View only.
2) The ViewModel subscribes to the Environment (as presented above) and publishes its own variables to the View. No need to use an #EnvironmentObject in your View then.
3) The Environment is injected into your View as an #EnvironmentObject and then is passed to the ViewModel.

Resources