Map view crashing on orientation change - ios

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!

Related

How to Show Alert from Anywhere in app SwiftUI?

I have condition to show alert in a view which can able to show from anywhere in the app. Like I want to present it from root view so it can possibly display in all view. Currently what happens when I present from very first view it will display that alert until i flow the same Navigation View. Once any sheets open alert is not displayed on it. Have any solutions in SwiftUI to show alert from one place to entire app.
Here is my current Implementation of code.
This is my contentView where the sheet is presented and also alert added in it.
struct ContentView: View {
#State var showAlert: Bool = false
#State var showSheet: Bool = false
var body: some View {
NavigationView {
Button(action: {
showSheet = true
}, label: {
Text("Show Sheet")
}).padding()
.sheet(isPresented: $showSheet, content: {
SheetView(showAlert: $showAlert)
})
}
.alert(isPresented: $showAlert, content: {
Alert(title: Text("Alert"))
})
}
}
Here from sheet I am toggle the alert and the alert is not displayed.
struct SheetView: View {
#Binding var showAlert: Bool
var body: some View {
Button(action: {
showAlert = true
}, label: {
Text("Show Alert")
})
}
}
here is the error in debug when we toggle button
AlertDemo[14187:3947182] [Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x109009c00> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x103908b50> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x103908b50>) which is already presenting <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x103d05f50>.
Any solution for that in SwiftUI? Thanks in Advance.
I was able to achieve this with this simplified version of what #workingdog suggested in their answer. It works as follows:
create the Alerter class that notifies the top-level and asks to display an alert
class Alerter: ObservableObject {
#Published var alert: Alert? {
didSet { isShowingAlert = alert != nil }
}
#Published var isShowingAlert = false
}
render the alert at the top-most level, for example in your #main struct or the ContentView
#main
struct MyApp: App {
#StateObject var alerter: Alerter = Alerter()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(alerter)
.alert(isPresented: $alerter.isShowingAlert) {
alerter.alert ?? Alert(title: Text(""))
}
}
}
}
set the alert that should be displayed from inside a child view
struct SomeChildView: View {
#EnvironmentObject var alerter: Alerter
var body: some View {
Button("show alert") {
alerter.alert = Alert(title: Text("Hello from SomeChildView!"))
}
}
}
Note on sheets
If you present views as sheets, each sheet needs to implement its own alert, just like MyApp does above.
If you have a NavigationView inside your sheet and present other views within this navigation view in the same sheet, the subsequent sheets can use the first sheet's alert, just like SomeChildView does in my example above.
Here is a possible example solution to show an Alert anywhere in the App.
It uses "Environment" and "ObservableObject".
import SwiftUI
#main
struct TestApp: App {
#StateObject var alerter = Alerter()
var body: some Scene {
WindowGroup {
ContentView().environment(\.alerterKey, alerter)
.alert(isPresented: $alerter.showAlert) {
Alert(title: Text("This is the global alert"),
message: Text("... alert alert alert ..."),
dismissButton: .default(Text("OK")))
}
}
}
}
struct AlerterKey: EnvironmentKey {
static let defaultValue = Alerter()
}
extension EnvironmentValues {
var alerterKey: Alerter {
get { return self[AlerterKey] }
set { self[AlerterKey] = newValue }
}
}
class Alerter: ObservableObject {
#Published var showAlert = false
}
struct ContentView: View {
#Environment(\.alerterKey) var theAlerter
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SecondView()) {
Text("Click for second view")
}.padding(20)
Button(action: { theAlerter.showAlert.toggle()}) {
Text("Show alert here")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondView: View {
#Environment(\.alerterKey) var theAlerter
var body: some View {
VStack {
Button(action: { theAlerter.showAlert.toggle()}) {
Text("Show alert in second view")
}
}
}
}

Use of unresolved identifier 'Map' SwiftUI

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

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 use Toast-Swift in SwiftUI?

I'd like to use this project Toast-Swift through CocoaPods in a SwiftUI view. It's for UIView, so I tried to write a ViewController and wrap it into SwiftUI but the result is nothing on the screen.
My code:
struct ToastView: UIViewControllerRepresentable{
#State var text: String
func makeUIViewController(context: UIViewControllerRepresentableContext<ToastView>) -> UIToast {
return UIToast(text: text)
}
func updateUIViewController(_ uiViewController: UIToast, context: UIViewControllerRepresentableContext<ToastView>) {
}
}
class UIToast: UIViewController{
var text: String = ""
init(text: String) {
super.init(nibName: nil, bundle: nil)
self.text = text
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.makeToast(text)
}
}
I've found some custom implementations on SO of a Toast for SwiftUI (SO question) but their behaviour is not exactly what I was looking for.
Can someone please help me to fix this? Is there another recommendation for Toast in SwiftUI? Thanks in advance!
I put it here for anyone who still looking for this subject using SwiftUI:
https://github.com/elai950/AlertToast
struct ContentView: View{
#State private var showAlert = false
var body: some View{
VStack{
Button("Show Alert"){
showAlert.toggle()
}
}
.toast(isPresenting: $showAlert){
// `.alert` is the default displayMode
AlertToast(displayMode: .alert, type: .regular, title: "Message Sent!")
//Choose .hud to toast alert from the top of the screen
//AlertToast(displayMode: .hud, type: .regular, title: "Message Sent!")
}
}
}
I don´t know if you have resolved your problem, but I´m posting this here in case someone is interested. I managed to do this getting a reference to the SceneManager, and then using its rootViewController view. With this view you can use Toast_Swift
Example:
Button(action: {
let scene = UIApplication.shared.connectedScenes.first
if let sceneDelegate : SceneDelegate = scene?.delegate as? SceneDelegate{
if let view = sceneDelegate.window?.rootViewController?.view{
view.makeToast("Text")
}
}
//...
Hope it helps.
Try to use this open source: https://github.com/huynguyencong/ToastSwiftUI . I found that it is very easy to use.
struct ContentView: View {
#State private var isShowingToast = false
var body: some View {
VStack(spacing: 20) {
Button("Show toast") {
self.isShowingToast = true
}
Spacer()
}
.padding()
// Just add a modifier to show a toast, with binding variable to control
.toast(isPresenting: $isShowingToast, dismissType: .after(3)) {
ToastView(message: "Hello world!", icon: .info)
}
}
}

Resources