The following code shows different Pickers.
I want to achieve: MenuPickerStyle with Sections and custom Label like in second example https://imgur.com/a/Q0uxEmh but it is not selectable because it is grayed out
When doing it with the Menu container as recommended in the internet the rows are grayed out. How can I get the wanted behavior?
In the code you can see 4 different examples. The second is the grayed out picker rows.
Tested in Xcode 13.2.1, iOS 15
struct ContentView: View{
#State var number_1 = 0
#State var number_2 = 0
#State var number_3 = 0
#State var number_4 = 0
let numbers = [0,1,2,3,4]
var body: some View{
VStack{
//Picker works, no Section --------------------------
Menu(content: {
Picker(selection: $number_1, content: {
ForEach(numbers,id: \.self){i in
Text("Number: \(i)").tag(i)
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_1)")
})
//Picker is grayed out ---------------------------
Menu(content: {
Picker(selection: $number_2, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_2)")
})
//Picker works, collapsed View not desired behavior --------
Menu(content: {
Picker(selection: $number_3, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
.pickerStyle(MenuPickerStyle())
}, label: {
Text("selected Number = \(number_3)")
})
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
}
}
}
You can put multiple Pickers in a Menu, each with a subset of the available values, and they’ll all work as expected:
Menu {
// First section, with values 0–2
Picker(selection: $num) {
ForEach(0..<3, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
// Second section, with values 3–5
Picker(selection: $num) {
ForEach(3..<6, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
} label: {
Text("selected Number = \(num)")
}
SwiftUI will even stick a separator between the Pickers automatically.
The simplest fix is for case 4 - just rebuild menu (force-refresh) on selection changed:
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
.id(number_4) // << here !!
Tested with Xcode 13.4 / iOS 15.5
I think I've got a workaround.
I tried to use a menu and use Buttons for manipulating the selection. The problem is the mark of the selection if there is already a Image in the label of the button I think.
struct ContentView: View{
#State var number = 0
let numbers = [1,2,3,4]
var body: some View{
Menu(content: {
ForEach(numbers,id:\.self){i in
Section{
Button(action: {
number = i
}, label: {
Text("Number: \(i)")
})
}
}
}, label: {
Text("The selection is: \(number)")
.frame(width: UIScreen.main.bounds.width, height: 20)
.border(Color.green)
})
}
}
I'm not 100% certain I understand, but if your goal is to build a picker that lets the user click on the title and then select an option from a menu, returning the selection to the screen I would suggest doing it as follows:
struct SwiftUIView: View {
#State var option:Int = 0
let numbers = [1,2,3,4]
var body: some View {
VStack {
//Picker to select the option
Picker("Option Picker", selection: $option) {
ForEach(numbers, id: \.self) {
number in
Text("Selection Number \(number)").tag(number)
}
}.pickerStyle(MenuPickerStyle())
//Text confirming the selection won't show before a pick is made
Text(option != 0 ? "You selected option number \(option)" : "")
}
}
}
Let me know if that works for you / is what you were looking for.
Related
I am trying to setup a picker, simple. I am successfully fetching an array of projects from firebase and populating the picker with the names of the projects. The problem that I am having is that I need to get the project id when I click the list but it's not doing anything after I click the option that I want. I tried to run it in a simulator and also on my iPhone and nothing happens after I make the selection. I am pretty sure I am not updating the picker and thus I am not updating the variable with the selected project id. I tried using the .onChange on the picker but nothing happens.
import SwiftUI
struct NewProjectView: View {
#ObservedObject var viewModel = ProjectViewModel()
#ObservedObject var clientViewModel = ClientFeedViewModel()
#Environment (\.dismiss) var dismiss
#State var projectName: String = "s"
var clientNameIsEmpty: Bool {
if projectName.count < 3 {
return true
} else {
return false
}
}
var clients: [Client] {
return clientViewModel.clients
}
#State var selectedClient: String = ""
var body: some View {
NavigationView {
VStack {
Picker("", selection: $selectedClient) {
ForEach(clients, id:\.self) {
Text($0.clientName)
//I need to exctract the project id so I can pass it on
}
}
.pickerStyle(.menu)
CustomTextField(text: $projectName, placeholder: Text("Client Name"), imageName: "person.text.rectangle")
.padding()
.background(Color("JUMP_COLOR")
.opacity(0.75)
)
.cornerRadius(10)
.padding(.horizontal, 40)
Text("Name must contain more than 3 characters")
.font(.system(.subheadline))
.foregroundColor(.gray.opacity(0.3))
.padding(.top, 30)
.toolbar {
ToolbarItem(placement: .navigationBarLeading, content: {
Button(action: {
dismiss()
}, label: {
Text("Cancel")
})
})
ToolbarItem(placement: .navigationBarTrailing , content: {
Button(action: {
viewModel.newProject(name: projectName)
dismiss()
}, label: {
Text("Save")
})
.disabled(clientNameIsEmpty)
})
}
}
}
.presentationDetents([.height(400)])
//.presentationDetents([.medium])
.presentationDragIndicator(.visible)
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
Here is the picker populated with the foo data: picker
Your selection variable $selectedClient needs to have a type that matches the tagged value of each item in the picker.
As you're not specifying an explicit .tag for your text, the ForEach creates an implicit one using what it's using for tracking its loop, which in this case looks like it's a Client.
You can either change selectedClient to be a type of Client, or tag your displayed subview with the string value to populate selectedClient with, e.g.:
ForEach(clients, id: \.self) { client in
Text(client.clientName)
.tag(client.clientID)
}
Also, if each client has a unique ID, you're better off using that as ForEach's identifier than \.self. You can either specify id: \.clientID, etc., to use a single attribute – or you can add Identifiable conformance to Client and make sure that it has an id value that is guaranteed to be unique.
import SwiftUI
import Firebase
struct NewProjectView: View {
#ObservedObject var viewModel = ProjectViewModel()
#ObservedObject var clientViewModel = ClientFeedViewModel()
#Environment (\.dismiss) var dismiss
#State var projectName: String = "s"
var clientNameIsEmpty: Bool {
if projectName.count < 3 {
return true
} else {
return false
}
}
var clients: [Client] {
return clientViewModel.clients
}
#State var selectedClient: Client = Client(id: "", clientName: "Blank", timestamp: Timestamp(), ownerId: "", ownerUsername: "")
var body: some View {
NavigationView {
VStack {
Picker("d", selection: $selectedClient) {
ForEach(clients, id:\.id) { client in
Text(client.clientName)
.tag(client)
//I need to exctract the project id so I can pass it on
}
}
.pickerStyle(.menu)
Text(selectedClient.id ?? "")
CustomTextField(text: $projectName, placeholder: Text("Client Name"), imageName: "person.text.rectangle")
.padding()
.background(Color("JUMP_COLOR")
.opacity(0.75)
)
.cornerRadius(10)
.padding(.horizontal, 40)
Text("Name must contain more than 3 characters")
.font(.system(.subheadline))
.foregroundColor(.gray.opacity(0.3))
.padding(.top, 30)
.toolbar {
ToolbarItem(placement: .navigationBarLeading, content: {
Button(action: {
dismiss()
}, label: {
Text("Cancel")
})
})
ToolbarItem(placement: .navigationBarTrailing , content: {
Button(action: {
viewModel.newProject(name: projectName)
dismiss()
}, label: {
Text("Save")
})
.disabled(clientNameIsEmpty)
})
}
}
}
.presentationDetents([.height(400)])
//.presentationDetents([.medium])
.presentationDragIndicator(.visible)
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
I want to open navigate to a view where I can add another picker option.
I tried the followings:
Picker(NSLocalizedString("Pick an option", comment: ""), selection: $value, content: {
ForEach(options, id: \.self){ option in
Text(option).tag(option)
}
Button(action: {
sheetIsPresented.toggle()
}, label: {
Image(systemName: "plus")
})
.sheet(isPresented: $sheetIsPresented){
AddView()
}
})
A button does show at the end of the options.
However, when the button is clicked, nothing happened. AddView() doesn't show up...
Is there a way to make this work?
try this approach, as shown in this example code:
struct ContentView: View {
#State var sheetIsPresented = false
#State var options = ["item-1","item-2","item-3"]
#State var value = "item-1"
var body: some View {
VStack (spacing: 88){
Picker(NSLocalizedString("Pick an option", comment: ""), selection: $value) {
ForEach(options, id: \.self){ option in
Text(option).tag(option)
}
}
Button(action: { sheetIsPresented.toggle() }) {
Image(systemName: "plus")
}
.sheet(isPresented: $sheetIsPresented) {
AddView(options: $options)
}
}
}
}
struct AddView: View {
#Binding var options: [String]
var body: some View {
// add some random option
Button(action: { options.append(UUID().uuidString) }) {
Image(systemName: "plus")
}
}
}
You can open a sheet with a menu if you put the .sheet on the menu element
example:
struct TestView: View {
#State var sheetIsPresented: Bool = false
var body: some View {
Menu("Actions") {
Button(action: {
sheetIsPresented.toggle()
}, label: {
Image(systemName: "plus")
})
}
.sheet(isPresented: $sheetIsPresented){
VStack{
Text("Hello")
}
}
}
}
I am trying to put a ProgressView inside a Picker label. When I tap the Hide Spinner button, this (intermittently) crashes with EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
struct ContentView: View {
#State private var selectedCity = ""
#State private var showSpinner = true
let cities = [
"Calgary",
"Edmonton",
"Toronto"
]
var body: some View {
NavigationView {
VStack(spacing: 0) {
Form {
Picker(selection: $selectedCity, label:
HStack {
Text("Your City")
if showSpinner {
ProgressView()
.padding(.horizontal, 2)
}
}
) {
ForEach(cities, id: \.self) { city in
Text(city).tag(city)
}
}
Button("Hide Spinner", action: { showSpinner = false })
}
}
.navigationBarTitle("ProgressView Crash", displayMode: .inline)
}
}
}
Am I doing anything wrong? I'm guessing this is a SwiftUI bug. I get the same behaviour when wrapping a UIActivityIndicatorView in a UIViewRepresentable.
Yes, it looks like a bug with auto-generated accessibility label. The safe workaround is to use explicitly provided accessibility.
Tested with Xcode 12 / iOS 14
Picker(selection: $selectedCity, label:
HStack {
Text("Your City")
if showSpinner {
ProgressView()
.padding(.horizontal, 2)
}
}.accessibility(label: Text("Your City")) // << here !!
) {
ForEach(cities, id: \.self) { city in
Text(city).tag(city)
}
}
I just try this code but I got unexpected edge for List.
If I remove navigationBarItems everything is ok.
import SwiftUI
let numbers: [String] = ["One", "Two", "Three"]
struct NavBarView: View {
var body: some View {
NavigationView {
List(numbers, id: \.self) { number in
SimpleRow(title: number)
}
.navigationBarTitle(Text("Numbers"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: { // add this will affect the List position and size
}, label: {Image(systemName: "plus").imageScale(.medium)}
))
}
}
}
struct SimpleRow: View {
var title: String
var body: some View {
HStack {
Text(title)
Spacer()
}
}
}
Interesting behavior, as it is only when modifier applied directly to List... even can't say if it is a bug... Maybe it is because navigationBarItems has deprecated.
Anyway here is possible solution - attach navigation bar items to something else, like background.
Tested with Xcode 12 / iOS 14
struct NavBarView: View {
var body: some View {
NavigationView {
List(numbers, id: \.self) { number in
SimpleRow(title: number)
}
.background(Color.clear
.navigationBarItems(trailing: Button(action: {
}, label: {Image(systemName: "plus").imageScale(.medium)}
)))
.navigationBarTitle(Text("Numbers"), displayMode: .inline)
}
}
}
i have this code.
unfortunately the pickerview is not centered horizontally and there is too much space between the buttons and the pickerview (vertically), i now i can use offset, but is there a better way?
var body: some View {
NavigationView {
Form {
Section(header: Text("Hi"), content: {
Button("Alphabet") {
}.frame(alignment: .center)
Button("Ok") {
}.frame(alignment: .center)
HStack {
Picker(selection: $sortedBy,
label: Text(""),
content: {
ForEach(p, id: \.self) { category in
Text(category)
}
}).pickerStyle(WheelPickerStyle())
}
})
}
}
}
#Reiner Fischer...here is the result of your proposol (unfortunately not centered)
The problem is your empty label
label: Text(""),
even if the label is empty it takes some space on the left side of the picker. You can check by just adding some text to the label.
To get rid of the label, adjust your code like this:
.pickerStyle(WheelPickerStyle())
.labelsHidden()
That will center your picker selections
Update 21.02.2020
Hi Chris, enclosed is the code, i tested and that centers the picker:
struct PickerView: View {
let p:[Vital] = [
.init(name: "0"),
.init(name: "1"),
.init(name: "2"),
.init(name: "3")
]
#State private var sortedby = 0
var body: some View {
Picker(selection: $sortedby, label: Text("")) {
ForEach(p) { post in
Text(post.name)
}
}.pickerStyle(DefaultPickerStyle())
.labelsHidden()
}
}