ARKit camera tracking state can’t show on SwiftUI - swift

I’m trying to show the camera tracking state info on top of device screen, but still get errors for all my efforts. I’m very confused with how ARView works with SwiftUI, for showing AR experience and adding button icons on top of it is simple, but to getting data from session just looks more work need to be done, could someone help me this? Thanks!
Error in ViewModel: ‘description is a get-only property’
Content View
import SwiftUI
import ARKit
import RealityKit
struct ContentView: View {
#StateObject var vm = ViewModel()
var body: some View {
ZStack{
ARViewContainer()
.edgesIgnoringSafeArea(.all)
.environmentObject(vm)
VStack{
Text(vm.sessionInfoLabel)
.font(.title3)
.foregroundColor(.red)
Spacer()
HStack{
Button{
} label: {
Image(systemName: "person.2")
.padding()
.font(.title)
}
Spacer()
Button{
} label: {
Image(systemName: "target")
.padding()
.font(.title)
}
}
.padding()
}
}
}
}
struct ARViewContainer: UIViewRepresentable {
#EnvironmentObject var vm: ViewModel
func makeUIView(context: Context) -> ARView {
return vm.arView
}
func updateUIView(_ uiView: ARView, context: Context) {
}
}
ViewModel
import SwiftUI
import ARKit
import RealityKit
class ViewModel: ObservableObject {
#Published var arView: ARView
var sessionInfoLabel = ""
init() {
arView = ARView.init(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
arView.session.delegate = arView
arView.session.run(config)
}
}
extension ARView: ARSessionDelegate {
public func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
camera.trackingState.description = ViewModel().sessionInfoLabel
}
}
extension ARCamera.TrackingState: CustomStringConvertible {
public var description: String {
switch self {
case .normal:
return "Normal"
case .notAvailable:
return "Not Available"
case .limited(.initializing):
return "Initializing"
case .limited(.excessiveMotion):
return "Excessive Motion"
case .limited(.insufficientFeatures):
return "Insufficient Features"
case .limited(.relocalizing):
return "Relocalizing"
case .limited:
return "Unspecified Reason"
}
}
}

Related

How to clean PKCanvas from strokes when I leave the screen?

I have a basic app, a Main View with links to Drawing View. When I go to the Drawing View №1 and paint something with Apple Pencil, then go back to Main View, then go back to Drawing View №1 - I still see my painting. It stayed in the memory.
Question: What is a proper way to free the memory from PKCanvas and it's strokes when leaving the view?
I know I can "remove" the drawing by assigning canvasView.drawing = PKDrawing() a new blank drawing. But does it really solve the problem of keeping junk in the memory?
Here is my bare-minimum code:
import SwiftUI
import PencilKit
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: DrawingView()) {
Text("Go to Drawing #1")
}
.padding()
NavigationLink(destination: DrawingView()) {
Text("Go to Drawing #2")
}
.padding()
}
}
}
}
struct DrawingView: View {
#State private var canvasView = PKCanvasView()
var body: some View {
PKCanvasViewRepresentable(canvasView: $canvasView)
.frame(width: 500, height: 500)
.border(Color.blue)
}
}
struct PKCanvasViewRepresentable: UIViewRepresentable {
#Binding var canvasView: PKCanvasView
func makeUIView(context: Context) -> PKCanvasView {
canvasView.tool = PKInkingTool(.pen, color: .black, width: 26)
canvasView.becomeFirstResponder()
canvasView.delegate = context.coordinator
return canvasView
}
func updateUIView(_ canvasView: PKCanvasView, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PKCanvasViewDelegate, UIPencilInteractionDelegate {
var canvas: PKCanvasViewRepresentable
init(_ canvas: PKCanvasViewRepresentable) {
self.canvas = canvas
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
print("canvasViewDrawingDidChange()")
}
}
}

How to make a text appear on the screen after pressing the button?

I want to make the text display on the screen according to the different scenes after pressing the button. For example, if model A is displayed, text "A" will be appeared on the screen. Similarly, if model B is displayed, text "B" will also be appeared. I am currently creating Augmented Reality app using SwiftUI interface and RealityKit but not sure what to do in the next step.
Here is my code:
import SwiftUI
import RealityKit
struct ContentView : View {
#State var arView = ARView(frame: .zero)
var body: some View {
ZStack {
ARViewContainer(arView: $arView)
HStack {
Spacer()
Button("information") {
print(self.arView.scene.name)
print(arView.scene.anchors.startIndex)
print(arView.scene.anchors.endIndex)
}
Spacer()
Button("remove") {
stop()
}
Spacer()
}
} .edgesIgnoringSafeArea(.all)
}
func stop() {
arView.scene.anchors.removeAll()
}
}
struct ARViewContainer: UIViewRepresentable {
#Binding var arView: ARView
func makeUIView(context: Context) -> ARView {
let boxAnchor = try! Experience1.loadBox()
let crownAnchor = try! Experience1.loadCrown()
arView.scene.anchors.append(boxAnchor)
arView.scene.anchors.append(crownAnchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) { }
}
From the code above, if boxAnchor and crownAnchor and displayed, text "Box" and "Crown" will be appeared on the screen respectively. Anyone who knows how to do that please guide me or suggest a tutorial that I can use to study.
Sorry if I use the wrong technical terms. Thank you
Use Combine's reactive subscriber and MVVM's bindings to update string values for Text views.
import SwiftUI
import RealityKit
import Combine
struct ContentView : View {
#State private var arView = ARView(frame: .zero)
#State private var str01: String = "...some text..."
#State private var str02: String = "...some text..."
var body: some View {
ZStack {
ARViewContainer(arView: $arView, str01: $str01, str02: $str02)
.ignoresSafeArea()
VStack {
Spacer()
Text(str01)
.foregroundColor(.white)
.font(.largeTitle)
Divider()
Text(str02)
.foregroundColor(.white)
.font(.largeTitle)
Spacer()
}
}
}
}
The miracle happens in the escaping closure of subscribe(to:) instance method. What will be the conditions in the if-statements is up to you.
struct ARViewContainer: UIViewRepresentable {
#Binding var arView: ARView
#Binding var str01: String
#Binding var str02: String
#State var subs: [AnyCancellable] = []
func makeUIView(context: Context) -> ARView {
let boxAnchor = try! Experience.loadBox()
let crownAnchor = try! Experience.loadCrown()
print(arView.scene.anchors.count)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
arView.scene.anchors.append(boxAnchor)
arView.scene.anchors.append(crownAnchor)
print(arView.scene.anchors.count)
}
return arView
}
func updateUIView(_ view: ARView, context: Context) {
DispatchQueue.main.async {
_ = view.scene.subscribe(to: SceneEvents.DidAddEntity.self) { _ in
if view.scene.anchors.count > 0 {
if view.scene.anchors[0].isAnchored {
str01 = "Crown"
str02 = "Cube"
}
}
}.store(in: &subs)
}
}
}

SwiftUI Button interact with Map

I'm totally new with Swift and SwiftUI and for a project group, I need to develop my first IOS app.
I can display a map with Mapbox but I don't know how to follow my user when I click on a button.
I don't know how to interact my button with my struct MapView
This is my code:
MapView.swift:
import Mapbox
struct MapView: UIViewRepresentable {
let mapView: MGLMapView = MGLMapView(frame: .zero)
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapView>) {
}
func styleURL(_ styleURL: URL) -> MapView {
mapView.styleURL = styleURL
return self
}
func centerCoordinate(_ centerCoordinate: CLLocationCoordinate2D) -> MapView {
mapView.centerCoordinate = centerCoordinate
return self
}
func zoomLevel(_ zoomLevel: Double) -> MapView {
mapView.zoomLevel = zoomLevel
return self
}
func userTrackingMode(_ userTrackingMode: MGLUserTrackingMode) -> MapView {
mapView.userTrackingMode = userTrackingMode
return self
}
}
class Coordinator: NSObject, MGLMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
ContentView.swift:
import Mapbox
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
ZStack {
MapView()
.userTrackingMode(.follow)
.edgesIgnoringSafeArea(.all)
HStack(alignment: .top) {
Spacer()
VStack() {
Button(action: {
//ACTION TO CHANGE FOLLOW MODE
}) {
Image(systemName: "location.fill")
.frame(width: 40.0, height: 40.0)
}
.padding(.top, 60.0)
.padding(.trailing, 10.0)
.frame(width: 45.0, height: 80.0)
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.colorScheme, .dark)
}
}
Actually I think in your case, as you have a reference to map, you can try to interact with it directly (aka imperatively, because it is such by nature, so no need to make simple thing complex)
Like
...
let myMapHolder = MapView()
var body: some View {
ZStack {
myMapHolder
.userTrackingMode(.follow)
.edgesIgnoringSafeArea(.all)
...
VStack() {
Button(action: {
self.myMapHolder.mapView.userTrackingMode = _your_mode_
}) {
Image(systemName: "location.fill")

Adding a TextField to NavigationBar with SwiftUI

I've been fooling around with Xcode 11 and SwiftUI for the past few hours, attempting to implement a TextField in the NavigationBar. Generally, the first "Hello, World"-type application I build is a simple web browser: TextField and WKWebView.
However, I'm having an exceptionally difficult time trying to implement the TextField in a fixed .inline NavigationBar. Furthermore, I can't seem to find a single tutorial or piece of code anywhere online. I've gone through pages and pages of Google, as well as projects on GitHub, with no luck.
The only results that mention this topic in specific are Reddit threads and forum discussion posts – all of which ask the same question: "Has anyone been able to successfully implement a TextField in the NavigationBar?" No one has yet to respond with a solution.
Here's my current ContentView.swift – I have removed all of my programmatic attempts at implementing a TextField as it either crashes or throws errors:
import SwiftUI
import WebKit
let address = "https://developer.apple.com"
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
WebView(request: URLRequest(url: URL(string: address)!))
.edgesIgnoringSafeArea(.bottom)
.edgesIgnoringSafeArea(.leading)
.edgesIgnoringSafeArea(.trailing)
}
.navigationBarTitle("TextField Placeholder", displayMode: NavigationBarItem.TitleDisplayMode.inline)
}
}
}
struct WebView: UIViewRepresentable {
let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
}
I don't know if it's exactly what you're trying to achieve, I think it might be a good solution:
import SwiftUI
import WebKit
let address = "https://developer.apple.com"
struct ContentView: View {
#State private var text = ""
var body: some View {
NavigationView {
GeometryReader { geometry in
VStack {
WebView(request: URLRequest(url: URL(string: address)!))
.edgesIgnoringSafeArea(.bottom)
.edgesIgnoringSafeArea(.leading)
.edgesIgnoringSafeArea(.trailing)
}
.navigationBarItems(leading:
HStack {
TextField("Type something here...", text: self.$text)
.background(Color.yellow)
}
.padding()
.frame(width: geometry.size.width)
.background(Color.green)
)
}
}
}
}
struct WebView: UIViewRepresentable {
let request: URLRequest
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
Copy-paste the code here above and let me know if I can try to improve my solution with something else. I coloured a couple of views of green and yellow just for a matter of debugging.
There is a new method in iOS 14+ that populates the toolbar or navigation bar with the specified items:
func toolbar<Content>(content: () -> Content) -> some View where Content : ToolbarContent
NavigationView {
GeometryReader { geometry in
ZStack {
Text("Hello")
}
.navigationBarTitle(" ", displayMode: .inline)
.navigationBarItems(leading:
HStack {
TextField("Seach for products, brands and more", text: self.$searchText)
}
.frame(width: geometry.size.width - 35,
height: 38,
alignment: .center)
.background(Color.white)
)
.background(NavigationConfigurator { nc in
nc.navigationBar.barTintColor = UIColor(red: 104.0/255.0, green: 194.0/255.0, blue: 25.0/255.0, alpha: 1.0)
})
}
}
.navigationViewStyle(StackNavigationViewStyle())
struct NavigationConfigurator: UIViewControllerRepresentable {
var configure: (UINavigationController) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
if let nc = uiViewController.navigationController {
self.configure(nc)
}
}
}

Get a UIViewControllerRepresentableContext environment value?

Ok,
so this might be trivial, but I am not sure how to go about this.
I have a UIViewController that gets created when the SwiftUI view calls:
func makeUIViewController(context: Context) -> MyViewController
The View that makes that call was given an environment object in the SceneDelegate like we have seen in the tutorials:
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(MyData()))
What I am trying to do is to use that environment object (MyData()) within my UIViewController logic. The ViewController would read/write on MyData's instance as needed, and from what I understand that should cause the SwiftUI view to react accordingly since MyData conforms to BindableObject...
So in the makeUIViewController call I get the UIViewControllerRepresentableContext. I can see the environment in the context:
context.environment
and if I print it in the console during debug I see this:
context.environment: [EnvironmentPropertyKey<PreferenceBridgeKey> = Value(value: Optional(SwiftUI.PreferenceBridge)), EnvironmentPropertyKey<FontKey> = Optional(SwiftUI.Font(provider: SwiftUI.(unknown context at $1c652cbec).FontBox<SwiftUI.Font.(unknown context at $1c656e2cc).TextStyleProvider>)), .......
In the print I see the MyData environmentObject instance:
EnvironmentPropertyKey<StoreKey<MyData>> = Optional(MyApp.MyData), ...
I am not sure how to get MyData out of the environment values given to me in the context.environment....
I have tried to figure out how to get the proper EnvironmentKey for MyData so I could try access it view subscript ... context.environment[myKey...]
How can I get MyData back from the environment values given to me by the context?
Using #EnvironmentObject now works (but not in Xcode Preview). Used Xcode 11.1/Swift 5.1. For simplicity it was used UIViewRepresentable, but the same should work for UIViewControllerRepresentable, because it is also SwiftUI View
Here is complete demo
import SwiftUI
import Combine
import UIKit
class AppState: ObservableObject {
#Published var simpleFlag = false
}
struct CustomUIView: UIViewRepresentable {
typealias UIViewType = UIButton
#EnvironmentObject var settings: AppState
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: UIButton.ButtonType.roundedRect)
button.setTitle("Tap UIButton", for: .normal)
button.actionHandler(controlEvents: UIControl.Event.touchUpInside) {
self.settings.simpleFlag.toggle()
}
return button
}
func updateUIView(_ uiView: UIButton, context: UIViewRepresentableContext<CustomUIView>) {
}
}
struct ContentView: View {
#ObservedObject var settings: AppState = AppState()
var body: some View {
VStack(alignment: .center) {
Spacer()
CustomUIView()
.environmentObject(self.settings)
.frame(width: 100, height: 40)
.border(Color.blue)
Spacer()
if self.settings.simpleFlag {
Text("Activated").padding().background(Color.red)
}
Button(action: {
self.settings.simpleFlag.toggle()
}) {
Text("SwiftUI Button")
}
.padding()
.border(Color.blue)
}
.edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(AppState())
}
}
/// Just utility below
extension UIButton {
private func actionHandler(action:(() -> Void)? = nil) {
struct __ { static var action :(() -> Void)? }
if action != nil { __.action = action }
else { __.action?() }
}
#objc private func triggerActionHandler() {
self.actionHandler()
}
func actionHandler(controlEvents control :UIControl.Event, for action:#escaping () -> Void) {
self.actionHandler(action: action)
self.addTarget(self, action: #selector(triggerActionHandler), for: control)
}
}
I had the same question and it was answered by Apples excellent tutorial https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit.
What you do basically is pass a binding into your ViewController.
import SwiftUI
import UIKit
struct PageViewController<Page: View>: UIViewControllerRepresentable {
var pages: [Page]
#Binding var currentPage: Int
...
Which is then passed the binding like so:
import SwiftUI
struct PageView<Page: View>: View {
var pages: [Page]
#State private var currentPage = 0
var body: some View {
ZStack(alignment: .bottomTrailing) {
PageViewController(pages: pages, currentPage: $currentPage)
// Here is the binding -^
PageControl(numberOfPages: pages.count, currentPage: $currentPage)
.frame(width: CGFloat(pages.count * 18))
.padding(.trailing)
}
}
}
This also works of course with #EnvironmentObject instead of #State