8

I have a watchOS app which has two screens. When I navigate to the second screen I get the following warning in the console:

ScrollView contentOffset binding has been read; this will cause grossly inefficient view performance as the ScrollView's content will be updated whenever its contentOffset changes. Read the contentOffset binding in a view that is not parented between the creator of the binding and the ScrollView to avoid this.

This seems to be related to using the @EnvironmentObject for the picker selection in the second screen. The warning does not happen if I remove the @EnvironmentObject and replace it with @State for the picker selection. (But then the updates are not reflected on the first screen).

Why is this happening? What can I do to stop this warning?

Here is my code:

First screen:

import SwiftUI

struct ContentView: View {
  @EnvironmentObject var itemManager: ItemManager

  var body: some View {
    List {
      ForEach(itemManager.items.indices) { index in
        NavigationLink(destination: ItemView(index: index)) {
          VStack(alignment: HorizontalAlignment.leading) {
            HStack {
              Text(self.itemManager.items[index].name)
              Spacer()
              Text("x")
              Text(String(self.itemManager.items[index].quantity))
            }
            Text(self.itemManager.items[index].type.rawValue).font(.footnote)
          }
        }
      }
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView().environmentObject(ItemManager())
  }
}

Second screen:

import SwiftUI

struct ItemView: View {

  @EnvironmentObject var itemManager: ItemManager
  var index: Int

  var body: some View {

    VStack {
      Text("Update")
      Form {
        Section {
          Picker(selection: $itemManager.items[index].type, label: Text("Food Type")) {
            ForEach(FoodType.allCases.sorted()) { type in
              Text(type.rawValue).tag(type)
            }
          }
        }
      }
    }.navigationBarTitle(Text("Item"))
  }
}

struct ItemView_Previews: PreviewProvider {
  static var previews: some View {

    return ItemView(index: 0).environmentObject(ItemManager())
  }
}

Model:

import Foundation

enum FoodType: String, CaseIterable, Identifiable, Comparable{
  case fruit
  case vegetable
  case poultry
  case bakery
  var id: FoodType{self}

  static func < (lhs: FoodType, rhs: FoodType) -> Bool {
    lhs.rawValue < rhs.rawValue
  }
}

struct Item {
  var type: FoodType
  var quantity: Int
  var name: String
}

class ItemManager: ObservableObject {

  @Published var items: [Item] =
    [
      Item(type: FoodType.fruit, quantity: 1, name: "apple"),
      Item(type: FoodType.bakery, quantity: 1, name: "french bread"),
      Item(type: FoodType.vegetable, quantity: 6, name: "carrots")
  ]
}

HostingController:

import WatchKit
import Foundation
import SwiftUI

class HostingController: WKHostingController<AnyView> {
    override var body: AnyView {
      return AnyView(ContentView()
      .environmentObject(ItemManager()))
    }
}

1 Answer 1

2

I ended up using the @State it the second screen (ItemView) and setting the @EnvironmentObject itemManager when the view disappears.

First Screen:

import SwiftUI

struct ContentView: View {
  @EnvironmentObject var itemManager: ItemManager

  var body: some View {
    List {
      ForEach(itemManager.items, id: \.self) { item in
        NavigationLink(destination: ItemView(selection: item.type, food: item)) {
          VStack(alignment: HorizontalAlignment.leading) {
            HStack {
              Text(item.name)
              Spacer()
              Text("x")
              Text(String(item.quantity))
            }
            Text(item.type.rawValue).font(.footnote)
          }
        }
      }
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView().environmentObject(ItemManager())
  }
}

Note: I got the var itemIndex: Int strategry from the Landmark apple tutorial

Second Screen:

import SwiftUI

struct ItemView: View {

  @EnvironmentObject var itemManager: ItemManager
  @State var selection : FoodType
  var food: Item
  var itemIndex: Int { 
    itemManager.items.firstIndex(where: { $0.id == food.id })!
  }

  var body: some View {

    VStack {
      Text("Update")
      Form {
        Section {
          Picker(selection: $selection, label: Text("Food Type")) {
            ForEach(FoodType.allCases.sorted()) { type in
              Text(type.rawValue).tag(type)
            }
          }
        }
      }
    }.navigationBarTitle(Text("Item"))
    .onDisappear {
      self.itemManager.items[self.itemIndex].type = self.selection
    }
  }
}

struct ItemView_Previews: PreviewProvider {
  static var previews: some View {
    let food = Item(id: UUID(), type: FoodType.fruit, quantity: 1, name: "apple")
    return ItemView(selection: FoodType.fruit, food: food)
  }
}

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.