SWIFTUI and Core Motion - swiftui

I am trying to display the accelerometer data in a SWIFTUI view. This code works to print to the console, but I can't understand how to get it inside a view so I can use it in SWIFTUI.
import SwiftUI
import CoreMotion
struct AccelerometerView: View {
let motionManager = CMMotionManager()
let queue = OperationQueue()
var body: some View {
VStack{
Text("accelerate:").onAppear {
print("ON APPEAR")
self.motionManager.startDeviceMotionUpdates(to: self.queue) { (data: CMDeviceMotion?, error: Error?) in
guard let data = data else {
print("Error: \(error!)")
return
}
let attitude: CMAttitude = data.attitude
print("pitch: \(attitude.pitch)")
print("yaw: \(attitude.yaw)")
print("roll: \(attitude.roll)")
}
}//.onappear
//Text("Pitch:\(attitude.pitch)")
}//Vstack
}//view
}//struct
struct AccelerometerView_Previews: PreviewProvider {
static var previews: some View {
AccelerometerView()
}
}

Here is possible solution
struct AccelerometerView: View {
let motionManager = CMMotionManager()
let queue = OperationQueue()
#State private var pitch = Double.zero
#State private var yaw = Double.zero
#State private var roll = Double.zero
var body: some View {
VStack{
Text("Pitch: \(pitch)")
Text("Yaw: \(yaw)")
Text("Roll: \(roll)")
}//Vstack
.onAppear {
print("ON APPEAR")
self.motionManager.startDeviceMotionUpdates(to: self.queue) { (data: CMDeviceMotion?, error: Error?) in
guard let data = data else {
print("Error: \(error!)")
return
}
let attitude: CMAttitude = data.attitude
print("pitch: \(attitude.pitch)")
print("yaw: \(attitude.yaw)")
print("roll: \(attitude.roll)")
DispatchQueue.main.async {
self.pitch = attitude.pitch
self.yaw = attitude.yaw
self.roll = attitude.roll
}
}
}//.onappear
}//view
}//struct

Related

How to loop through API call data and display in view

I know I can use for each for this, but every time I try to implement according to documentation it throws some kind of error regarding syntax.
Here is my view:
import SwiftUI
import Combine
struct HomeTab: View {
#StateObject var callDevices = CallDevices()
var body: some View {
NavigationView {
devices
.onAppear {
callDevices.getDevices()
}
}
}
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(content: callDevices.getDevices(), id: \.self) { device in
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
}
struct HomeTab_Previews: PreviewProvider {
static var previews: some View {
HomeTab()
}
}
Here is my Call Devices which works without issue in other views:
class CallDevices: ObservableObject {
private var project_id: String = "r32fddsf"
#Published var devices = [Device]()
func getDevices() {
guard let url = URL(string: "www.example.com") else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Authorization")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {print(error!.localizedDescription); return }
// guard let data = data else {print("empty data"); return }
let theData = try! JSONDecoder().decode(Welcome.self, from: data!)
DispatchQueue.main.async {
self.devices = theData.devices
}
}
.resume()
}
}
is the issue in the way I am calling my function?
try this:
(you may need to make Device Hashable)
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(callDevices.devices, id: \.self) { device in // <-- here
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
If Device is Identifiable, you can remove the id: \.self.
struct Device: Identifiable, Hashable, Codable {
let id = UUID()
var Name = ""
var Status = ""
// ... any other stuff you have
}

Calling Method on Child View in SwiftUI

I have an ImageEditView that contains an ImageCanvasView and an ImageCaptureButton.
Ideally, I want ImageCaptureButton to call a method on ImageCanvasView called takeScreenshot.
How do I achieve this in SwiftUI? I've been thinking of trying to save ImageCanvasView into a variable in ImageEditView so that my ImageCaptureButton can then call its method, but SwiftUI's declarative nature means this isn't possible.
----- EDIT below -----
The flow is as follows:
ImageSelectView (user selects an image)
ImageEditView (user edits an image) - this view contains ImageCanvasView and ImageCaptureButton
ImageShareView (user shares the image)
The following is ImageEditView
import SwiftUI
struct ImageEditView: View {
#State var selectedImage: Image
#State private var isNavLinkPresented = false
#State private var imageSnapshot: UIImage = UIImage()
var canvasView: ImageCanvasView = ImageCanvasView(selectedImage: $selectedImage)
// this won't work: Cannot use instance member '$selectedImage' within property initializer; property initializers run before 'self' is available
var body: some View {
VStack {
canvasView
Spacer()
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: ImageShareView(imageToShare: $imageSnapshot), isActive: $isNavLinkPresented) {
Text("Next")
.onTapGesture {
imageSnapshot = canvasView.takeScreenshot()
isNavLinkPresented = true
}
}
}
}
.navigationBarTitle(Text("Edit image"))
}
}
You are in the right direction. By creating var of the view, you can call the function.
Here is the example demo
struct ImageEditView: View {
var canvasView: ImageCanvasView = ImageCanvasView()
var body: some View {
VStack {
Text("ImageEditView")
canvasView
Button("ImageCaptureButton") {
canvasView.takeScreenshot()
}
}
}
}
struct ImageCanvasView: View {
var body: some View {
Text("ImageCanvasView")
}
func takeScreenshot() {
print(#function + " Tapped ")
}
}
You can also use computed property to pass data
struct ImageEditView: View {
var data: String = ""
var canvasView: ImageCanvasView {
ImageCanvasView(data: data)
}
// Body code
}
struct ImageCanvasView: View {
var data: String
var body: some View {
Text("ImageCanvasView")
}
func takeScreenshot() {
print(#function + " Tapped ")
}
}
EDIT
Use init to use the same instance of ImageCanvasView.
struct ImageEditView: View {
#State var selectedImage: Image
#State private var isNavLinkPresented = false
#State private var imageSnapshot: UIImage = UIImage()
var canvasView: ImageCanvasView!
init(selectedImage: Image) {
self.selectedImage = selectedImage
canvasView = ImageCanvasView(selectedImage: $selectedImage)
}
// Other code

Cannot get view to update with downloaded JSON

I am stuck at the simplest place right now.. I'm making a network request and just want the view to be updated once the JSON is received..
And I verified that:
JSON is valid
Valid response received (verified in print statement)
I've done this about 50 times and don't know why I'm stuck at this point.
struct ContentView: View {
#StateObject var nm = NetworkManager()
var body: some View {
VStack {
ScrollView {
HStack {
ForEach(nm.articles, id: \.hashValue) { article in
Text("Hello")
}
}.task {
do {
try await NetworkManager().getAllArticles(for: "mario")
} catch { print(error) }
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
final class NetworkManager: ObservableObject {
#Published var newsItem: News?
#Published var articles: [Article] = []
private let apiKey = ""//removed
private var baseUrlString: String {
"https://newsapi.org/v2/"
}
func getAllArticles(for searchItem: String) async throws {
let url = URL(string: baseUrlString + "everything?q=\(searchItem)&apiKey=\(apiKey)")!
let (data, _) = try await URLSession.shared.data(from: url)
let news = try JSONDecoder().decode(News.self, from: data)
DispatchQueue.main.async {
self.newsItem = news
self.articles = self.newsItem!.articles
}
}
}
struct News: Codable {
var totalResults: Int?
let articles: [Article]
}
struct Article: Codable, Hashable {
let author: String?
let title: String
let description: String
let url: String
let urlToImage: String?
let publishedAt: String
let content: String
}
Edit: removed apiKey
getAllArticles() is view-related, which means you should probably implement this function inside the View instead of ObservableObject.
struct ContentView: View {
#StateObject var nm = NetworkManager()
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(nm.articles, id: \.hashValue) { article in
Text("Hello")
}
}.task {
do {
try await getAllArticles(for: "mario")
} catch { print(error) }
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
extension ContentView {
func getAllArticles(for searchItem: String) async throws {
let url = URL(string: nm.baseUrlString + "everything?q=\(searchItem)&apiKey=\(NetworkManager.apiKey)")!
let (data, _) = try await URLSession.shared.data(from: url)
let news = try JSONDecoder().decode(News.self, from: data)
// Not necessary
// DispatchQueue.main.async {
nm.newsItem = news
nm.articles = nm.newsItem!.articles
// }
}
}
final class NetworkManager: ObservableObject {
#Published var newsItem: News?
#Published var articles: [Article] = []
static let apiKey = "YOUR_API_KEY"
var baseUrlString: String {
"https://newsapi.org/v2/"
}
}

SwiftUI How to toggle a Bool in a Struct from an ObservableObject class and show alert to notify user

I have a method in my class that opens a map when given an address string. Trying to show an alert in a view by toggling a boolean in a method in the class. I can't figure out how to toggle the boolean in the class method. This is what I tried. The Published bool in class method updates but does not update in the View. I did put up a repo of just this feature if anybody wants to play around with it.
https://github.com/Ongomobile/LocationTest/tree/main/LocationTest
import SwiftUI
#main
struct LocationTestApp: App {
var body: some Scene {
WindowGroup {
ContentView(location: LocationManager())
}
}
}
Here is my Class:
import UIKit
import MapKit
import CoreLocation
import Combine
class LocationManager: NSObject, ObservableObject {
var locationManager = CLLocationManager()
lazy var geocoder = CLGeocoder()
#Published var locationString = "1140"
// #Published var locationString = "1 apple park way cupertino"
#Published var currentAddress = ""
#Published var isValid: Bool = true
func openMapWithAddress () {
geocoder.geocodeAddressString(locationString) { placemarks, error in
if let error = error {
self.isValid = false
// prints false but does not update
print("isValid")
print(error.localizedDescription)
}
guard let placemark = placemarks?.first else {
return
}
guard let lat = placemark.location?.coordinate.latitude else{return}
guard let lon = placemark.location?.coordinate.longitude else{return}
let coords = CLLocationCoordinate2DMake(lat, lon)
let place = MKPlacemark(coordinate: coords)
let mapItem = MKMapItem(placemark: place)
mapItem.name = self.locationString
mapItem.openInMaps(launchOptions: nil)
}
}
}
Here is the view:
import SwiftUI
struct ContentView: View {
#ObservedObject var locationManager = LocationManager()
#State private var showingAlert = false
var body: some View {
Button {
locationManager.openMapWithAddress()
} label: {
Text("Get Map")
}
.alert(isPresented: $showingAlert) {
Alert(title: Text("Important message"), message:
Text("Enter a valid address"), dismissButton:
.default(Text("OK")))
}
}
}
I updated this answer to reflect some refactor help that I got from #rlong405 I put up a repository with this solution maybe it could help others.
OpenMapsInSwiftUI
import SwiftUI
struct ContentView: View {
#ObservedObject var locationManager = LocationManager()
var body: some View {
VStack{
Form{
Section {
Text("Enter Address")
TextField("", text: $locationManager.locationString)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.horizontal)
}
Button {
locationManager.openMapWithAddress()
} label: {
Text("Get Map")
}
.alert(isPresented: $locationManager.invalid) {
Alert(title: Text("Important message"), message:
Text("Enter a valid address"), dismissButton:
.default(Text("OK"), action:{
locationManager.invalid = false
locationManager.locationString = ""
}))
}
}
}
}
}
import UIKit
import MapKit
import CoreLocation
import Combine
class LocationManager: NSObject, ObservableObject {
lazy var geocoder = CLGeocoder()
#Published var locationString = ""
#Published var invalid: Bool = false
func openMapWithAddress () {
geocoder.geocodeAddressString(locationString) { placemarks, error in
if let error = error {
DispatchQueue.main.async {
self.invalid = true
}
print(error.localizedDescription)
}
guard let placemark = placemarks?.first else {
return
}
guard let lat = placemark.location?.coordinate.latitude else{return}
guard let lon = placemark.location?.coordinate.longitude else{return}
let coords = CLLocationCoordinate2DMake(lat, lon)
let place = MKPlacemark(coordinate: coords)
let mapItem = MKMapItem(placemark: place)
mapItem.name = self.locationString
mapItem.openInMaps(launchOptions: nil)
}
}
}

SwiftUI can not update class data updates

I came across a situation that you use class data as your data source, and display them in a swiftUI list view, when you update your data source, the swiftUI list view won't be updated, what can we do to make the class data updates interactive with swiftUI?
see code blow:
I define the environment object :
import Foundation
import Combine
class DataSource: ObservableObject {
public static let shared = DataSource()
#Published var datalist: [RowData] = []
func fetch() -> Void {
for n in 1...50 {
let data = RowData(title: "Index:\(n)", count: 0)
datalist.insert(data, at: 0)
}
}
func update() {
for data in datalist {
data.count = data.count+1
print("\(data.title) update count to :\(data.count)")
data.objectWillChange.send()
}
self.objectWillChange.send()
}
}
to display each data in a Row View:
import SwiftUI
struct RowView: View {
#State var data: RowData
var body: some View {
HStack{
Text(data.title)
Spacer()
Text("\(data.count)")
}.padding()
}
}
struct RowView_Previews: PreviewProvider {
static var previews: some View {
RowView(data: RowData(title: "text", count: 1))
}
}
class RowData: ObservableObject {
var title: String = ""
var count: Int = 0
init(title: String, count: Int) {
self.title = title
self.count = count
}
}
in content view, display the data in a list view, I would like to refresh all the view updates when click update button. the button triggers the update methods to update the class data value from data source.
struct ContentView: View {
#EnvironmentObject var data: DataSource
#State var shouldUpdate:Bool = false
#State var localData:[RowData] = []
var body: some View {
VStack {
Button(action: {
// your action here
self.data.update()
self.shouldUpdate.toggle()
self.localData.removeAll()
self.localData = self.data.datalist
}) {
Text("update")
}
List {
ForEach(0..<self.localData.count, id:\.self) { index in
RowView(data: self.localData[index])
}
}
}
}
}
Well... I don't see the reason to have localData, but, anyway, here is modified code that works.
Tested with Xcode 12 / iOS 14
class DataSource: ObservableObject {
public static let shared = DataSource()
#Published var datalist: [RowData] = []
func fetch() -> Void {
for n in 1...50 {
let data = RowData(title: "Index:\(n)", count: 0)
datalist.insert(data, at: 0)
}
}
func update() {
for data in datalist {
data.count = data.count+1
print("\(data.title) update count to :\(data.count)")
}
self.objectWillChange.send()
}
}
struct RowView: View {
#ObservedObject var data: RowData
var body: some View {
HStack{
Text(data.title)
Spacer()
Text("\(data.count)")
}.padding()
}
}
class RowData: ObservableObject {
#Published var title: String = ""
#Published var count: Int = 0
init(title: String, count: Int) {
self.title = title
self.count = count
}
}
struct ContentView: View {
#EnvironmentObject var data: DataSource
#State var localData:[RowData] = []
var body: some View {
VStack {
Button(action: {
// your action here
self.data.update()
self.localData = self.data.datalist
}) {
Text("update")
}
List {
ForEach(0..<self.localData.count, id:\.self) { index in
RowView(data: self.localData[index])
}
}
}
.onAppear {
self.data.fetch()
self.localData = self.data.datalist
}
}
}