2

I'm trying to learn SwiftUI and I'm going to develop a simple app with tab views and sharing core motion data between those views.

The main idea is to create a motion manager object (like here) and use the sensor values in all views.

ContentView.swift:

import SwiftUI

struct ContentView: View {

@State private var selection = 1

@State private var viewNames : [String] = ["View1", "View2"]

var body: some View {
    
    TabView(selection: $selection){
        
        View1(viewName: $viewNames[0]).tag(0)
        
        View2(viewName: $viewNames[1]).tag(1)
                    
        }
    }
}

View1.swift:

import SwiftUI

struct View1 : View {

@Binding var viewName : String

var body: some View {
    
    Text("First View")
    .font(.title)
    .foregroundColor(Color.gray)
    .tabItem {
        VStack {
            Image(systemName: "star")
            Text(viewName)
        }
     }
  }
}

View2.swift:

struct View2 : View {

@Binding var viewName : String


var body: some View {
    
    VStack {
        Text("Second View")
            .font(.title)
            .foregroundColor(Color.green)
            .padding(.top)
        
        View21(motionManager: MotionManager())

        
        
    }.tabItem {
        VStack {
            Image(systemName:"heart")
            Text(viewName)
        }
    }
    
  }

}

View21.swift

struct View21 : View {

@ObservedObject var motionManager : MotionManager

@State private var showDetails = false


var body: some View{
  Text(String(format: "%.2f", motionManager.x))
}

With these code I can use the sensor data in View21, but I can't access the data in the views in the hierarchy above.

Furthermore I created @ObservedObject in the ContentView (like here) and passed it through all views. My app work in the simulator, but it doesn't on the real device. I can see sensor data changing, but I can't switch the tab views. I've tried to use @EnvironementObject instead of @ObservedObject, but the behavior is the same.

I'll be very thankful for any help and tipps to my issue. Best wishes

Murokill
  • 87
  • 1
  • 7
  • Use the environment and inject your motion manager at the root of your view hierarchy. Show how you did this – Paulw11 Aug 24 '20 at 20:38
  • I tried two ways. 1. I added `var motionManager = MotionManager()` in the `SceneDelegate.swift`, I completed this `let contentView = ContentView().environment(motionManager)` and in the root view (`ContentView`), added `@EnvironmentObject var motionManager : MotionManager`. 2. I did nothing in `SceneDelegete`, but I've created a new object in the root view `var motionManager = MotionManager()`. – Murokill Aug 24 '20 at 20:50
  • The first way is correct. What does "I can't switch the tab views" mean? – Paulw11 Aug 24 '20 at 21:09
  • I‘ll prefer the first way. Is this the right way to pass the object to the next view: ‚View1(viewName: $viewNames[0])encironment(motionManager).tag(0)‘? That means, while clicking on the tab view icon on the bottom of screen - the app doesn‘t react immediately. I found out, the tab will activated by clicking for a long time on the same icon... In the simulator it works well without time delay. – Murokill Aug 24 '20 at 21:29
  • @Paulw11 I found out the reason of the issue. The sensor updating happens in the .main queue and block my UI. I decreased sensor update rate to 1hz and it was possible to change the view tabs... It's probably an another issue, but do you have any idea how can I move sensor updating routine to background and show the sensor dat in the ui? – Murokill Aug 25 '20 at 19:13

1 Answers1

3

Okay, so Paulw11 is right that the you probably want to inject your ObservableObject into your environment, then in each view that wants to access that instance, you just add a property with the @EnvironmentObject property wrapper. Below I've thrown together the simplest example I could think of, so that you can get the idea of how it works.

import SwiftUI
import Combine

class ManagerPlaceholder: ObservableObject {
    
    @Published var propertyOne: Double = 1.0
    @Published var propertyTwo: Double = 2.0
    
    func action() {
        propertyOne = Double.random(in: 0.0..<100.00)
        propertyTwo = Double.random(in: 0.0..<100.00)
    }
    
}


struct ContentView: View {
    
    @EnvironmentObject var manager: ManagerPlaceholder
    
    var body: some View {
        TabView {
            Subview()
                .tabItem { Label("First", systemImage: "hexagon.fill") }
                .tag(1)
            Subview()
                .tabItem { Label("Second", systemImage: "circle.fill") }
                .tag(2)
            
        }
    }
    
}

struct Subview: View {
    
    @EnvironmentObject var manager: ManagerPlaceholder
    
    var body: some View {
        VStack {
            Text("Prop One: \(manager.propertyOne)").padding()
            Text("Prop Two: \(manager.propertyTwo)").padding()
            Button("Change", action: manager.action).padding()
        }
    }
    
}

So above is

  • A simple ObservableObject - All it does is set two Doubles with a random value (notice the properties you want to observe are marked as @Published)
  • A tab view
  • A simple sub-view

Notice that both of the views have a @EnvironmentObject var manager: ManagerPlaceholder. You don't set that property directly. That line says that you want to reference a ManagerPlaceholder instance that's in the Environment. The environment is a sort of "pool" of storage that SwiftUI manages for you. You can add an instance of an object to it, then reference it in sub-views that need it.

So to make sure that's in the environment you add it when instantiating a view (could be the tab view, or any super-view). So for example in your _NAME_App.swift (for an iOS 14 target) or SceneDelegate.swift (for an iOS 13 target), you'd instantiate your view like this:

ContentView().environmentObject(ManagerPlaceholder())

If you run the code above you'll see when you hit the Change button it randomly sets the two properties, and both subviews will see the exact same values when you switch back and forth, because they're both referencing the same instance.

Feel free to comment if anything is unclear.

RogerTheShrubber
  • 986
  • 8
  • 19
  • Thanks for your great answer! I tested this example in the simulator and on the real device and I understood the main idea behind the environment handling (I hope so...). I'm going to adapt the changes to my app. – Murokill Aug 25 '20 at 05:36