I can't seem to get a SwiftUI Picker to work as expected.
I created a TestView below that more or less copies the example from Apple's Docs, but I'm still getting "'Picker' cannot be constructed because it has no accessible initializers" as an error.
Any idea how to resolve this? My weak attempt to make the enum public did nothing.
import SwiftUI
public enum Flavor: String, CaseIterable, Identifiable {
case chocolate
case vanilla
case strawberry
public var id: String { self.rawValue }
}
struct TestView: View {
#State private var selectedFlavor = Flavor.chocolate
var body: some View {
Picker("Flavor", selection: $selectedFlavor) {
Text("Chocolate").tag(Flavor.chocolate)
Text("Vanilla").tag(Flavor.vanilla)
Text("Strawberry").tag(Flavor.strawberry)
}
Text("Selected flavor: \(selectedFlavor.rawValue)")
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
XCode version: 13.2.1
As pointed out by jnpdx, I had a Picker enum previously created for an Image Picker in my app.
Renaming this enum to something else allowed me to use Apple's Picker without any errors.
Related
I have a very similar problem to Picker for optional data type in SwiftUI?.
The difference is, that I'm referencing an optional in an obserable class.
My code looks like:
enum Flavor: String, CaseIterable, Identifiable {
case chocolate
case vanilla
case strawberry
var id: String { self.rawValue }
}
class cl1: ObservableObject {
#Published var fl: Flavor?
}
struct ContentView: View {
#State private var selectedFlavor: cl1 = cl1()
var body: some View {
Picker("Flavor", selection: $selectedFlavor.fl) {
Text("Chocolate").tag(Flavor.chocolate as Flavor?)
Text("Vanilla").tag(Flavor.vanilla as Flavor?)
Text("Strawberry").tag(Flavor.strawberry as Flavor?)
}
.padding()
}
}
Even though I followed the other answers, as soon as I use a class object it fails.
What do I need to change to make it working?
When using an ObservableObject, you should be using a #StateObject property wrapper instead of #State -- this will allow your View to watch for updates on the #Published properties of the ObservableObject
#StateObject private var selectedFlavor: cl1 = cl1()
hi am having issues with the picker view in swiftui
i have created one file with just a class like this
import Foundation
import SwiftUI
class something: ObservableObject {
#Published var sel = 0
}
and then I created 2 views
import SwiftUI
struct ContentView: View {
#StateObject var hihi: something
var characters = ["Makima", "Ryuk", "Itachi", "Gojou", "Goku", "Eren", "Levi", "Jiraya", "Ichigo", "Sukuna"]
var body: some View {
VStack {
Section{
Picker("Please choose a character", selection: $hihi.sel) {
ForEach(characters, id: \.self) { name in
Text(name)
}
}
Text(characters[hihi.sel])
}
now(hihi: something())
}
}
}
struct now: View {
#StateObject var hihi: something
var body: some View {
Text("\(hihi.sel)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(hihi: something())
}
}
now the problem am facing is that the code compiles but the picker ain't working it won't change to any other value in the array I have provided it recoils back to its original value provided that is 0th index "Makima" and it won't select any other option, why so?
please help
There are three problems, the main one being the mismatching selection.
In the Picker, your selection is based on the string value for each character. This is because the ForEach identifies each Text by the name string, since you used id: \.self.
However, your something model (which ideally should start with a capital letter by convention) has a numeric selection. Because the items in the Picker have String IDs, and this is an Int, the selection can't be set.
You can change your model to this:
class something: ObservableObject {
#Published var sel = "Makima"
}
Which also requires a slight change in the body:
VStack {
Section{
Picker("Please choose a character", selection: $hihi.sel) {
ForEach(characters, id: \.self) { name in
Text(name)
}
}
Text(hihi.sel) // Now getting string directly
}
now(hihi: something())
}
Notice we now have two views showing the selected character - but only the top one updates. The bottom one may now be redundant (the now view), but I'll show you how you can get it working anyway. This is where we encounter the 2nd problem:
You are creating a new instance of something() when passing it to now (again, should start with a capital). This means that the current instance of hihi stored in ContentView is not passed along. You are just creating a new instance of something, which uses the default value. This is completely independent from the hihi instance.
Replace:
now(hihi: something())
With:
now(hihi: hihi)
The final problem, which may not be as visible, is that you shouldn't be using #StateObject in now, since it doesn't own the object/data. Instead, the object is passed in, so you should use #ObservedObject instead. Although the example now works even without this change, you will have issues later on when trying to change the object within the now view.
Replace #StateObject in now with #ObservedObject.
Full answer (something is initialized in ContentView only for convenience of testing):
struct ContentView: View {
#StateObject var hihi: something = something()
var characters = ["Makima", "Ryuk", "Itachi", "Gojou", "Goku", "Eren", "Levi", "Jiraya", "Ichigo", "Sukuna"]
var body: some View {
VStack {
Section{
Picker("Please choose a character", selection: $hihi.sel) {
ForEach(characters, id: \.self) { name in
Text(name)
}
}
Text(hihi.sel)
}
now(hihi: hihi)
}
}
}
struct now: View {
#ObservedObject var hihi: something
var body: some View {
Text(hihi.sel)
}
}
class something: ObservableObject {
#Published var sel = "Makima"
}
I have a enum that defines the language index. I put this enum in a file named Language.swift
enum Language: Int {
case en
case zh_hant
}
I have tried to declare a global variable that stores the current language.
final class ModelData: ObservableObject {
#Published var currentLanguage: Language = Language.zh_hant
#Published var globalString: [String] = load("strings.json")
}
However, when I tried to access that, I have the following error:
struct HomeView: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
Text("\(modelData.currentLanguage.rawValue)") // error, see the screen capture
Text("\(modelData.globalString.count)") // no problem
}
}
Yet, I used the same way to access the array, there is no problem.
The above error can be resolved by moving enum Language to be in the same file as class ModelData.
Yet, another problem was then identified.
I tried to do this in my code:
var languageIndex: Int {
modelData.currentLanguage.rawValue
}
var body: some View {
Text("\(modelData.globalString[languageIndex])") // preview cause "updating took more than 5 seconds]
}
My global String like this
["Hello", "你好"]
The problem appears in the Canvas view on preview the UI.
Yet, it seems to work fine under simulator. Any idea?
The problem appears in the Canvas view on preview the UI. Yet, it seems to work fine under simulator.
You have to set the environment object in the preview
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
.environmentObject(ModelData())
}
}
I am encountering the following SwiftUI error with #EnvironmentObject when used with a custom Shape, :
Fatal error: No ObservableObject of type MyObject found. A View.environmentObject(_:) for MyObject may be missing as an ancestor of this view.: file SwiftUI, line 0
It only happens when I use any Shape method that returns a new copy of the instance like stroke().
Here is a Swift playground example to reproduce:
import SwiftUI
import PlaygroundSupport
class MyObject: ObservableObject {
#Published var size: Int = 100
}
struct MyShape: Shape {
#EnvironmentObject var envObj: MyObject
func path(in rect: CGRect) -> Path {
let path = Path { path in
path.addRect(CGRect(x: 0, y: 0,
width: envObj.size, height: envObj.size))
}
return path
}
}
struct MyView: View {
var body: some View {
MyShape().stroke(Color.red) // FAIL: no ObservableObject found
// MyShape() // OK: it works
}
}
let view = MyView().environmentObject(MyObject())
PlaygroundPage.current.setLiveView(view)
As it looks like environment field is not copied, I've also tried to do it explicitly like this:
struct MyView: View {
#EnvironmentObject var envObj: MyObject
var body: some View {
MyShape().stroke(Color.red).environmentObject(self.envObj)
}
}
It still fails. As a SwiftUI beginner, I don't know whether this is the expected behavior, not inheriting the view hierarchy environment, and how to handle it - other than not using the environment.
Any idea?
The problem actually is that .stroke is called right after constructor, so before environmentObject injected (you can test that it works if you comment out stroke). But .stroke cannot be added after environment object injected, because .stroke is Shape-only modifier.
The solution is to inject dependency during construction as below. Tested with Xcode 11.4 / iOS 13.4
struct MyShape: Shape {
#ObservedObject var envObj: MyObject
...
}
struct MyView: View {
#EnvironmentObject var envObj: MyObject
var body: some View {
MyShape(envObj: self.envObj).stroke(Color.red)
}
}
I am facing issues setting up Binding in the following SwiftUI code snippet. I am trying this on xCode Beta 7 ((11M392r).
In the code snippet below, I am creating 2 Stepper views.
If I pass $student.totalMarks to Stepper, it works and creates the right Binding.
But if I try to access $student.marks.score1, that does not work and shows the following compilation error:
Generic parameter 'Subject' could not be inferred.
Is there a way to pass single field from a nested property into a binding?
struct Marks {
public let score1: Int
public let score2: Int
public let score3: Int
}
class Student: ObservableObject {
#Published var totalMarks: Int = 145
#Published var marks = Marks(score1: 67, score2: 56, score3: 64)
}
struct ContentView: View {
#ObservedObject var student = Student()
var body: some View {
return VStack {
Stepper("Total Score: \(student.totalMarks)", value: $student.totalMarks)
Stepper("Score 1: \(student.marks.score1)", value: $student.marks.score1)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Easy. ;-) Don't use a constant for a stepper's value binding. Rather make your scores variables (using var instead of let in struct Marks).