I am trying to run a function after a user has selected a picker option.
The idea is that the user can set a default station, so I need to be able to send the selected value to a function so I can save it inside the core data module. How can I achieve this?
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedFrameworkIndex, label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}.onReceive([self.frameworks].publisher.first()) { value in
self.doSomethingWith(value: value)
}
}
}
.navigationBarTitle("Settings")
}
}
}
Instead of using onRecive try using onChange.
Your code:
.onReceive([self.frameworks].publisher.first()) { value in
self.doSomethingWith(value: value)
}
Correction:
.onChange(of: $selectedFrameworkIndex, perform: { value in
self.doSomethingWith(value: value)
})
this will trigger every time $selectedFrameworkIndex is changed.
I would use a simple .onChange(of:) call on the picker. It will pick up on the change of the #State var and allow you to act on it. Use it like this:
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedFrameworkIndex, label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}
// Put your function call in here:
.onChange(of: selectedFrameworkIndex) { value in
print(value)
}
}
}
}
.navigationBarTitle("Settings")
}
}
}
yours:
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedFrameworkIndex, label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}.onReceive([self.frameworks].publisher.first()) { value in
self.doSomethingWith(value: value)
}
}
}
.navigationBarTitle("Settings")
}
}
}
add a variable that checks to see if selectedFrameworkIndex has changed.
change:
import SwiftUI
struct SettingsView: View {
var frameworks = ["DRN1", "DRN1 Hits", "DRN1 United", "DRN1 Life"]
#State private var selectedFrameworkIndex = 0
#State private var savedFrameworkIndex = 0
func handler<Value: Any>(val: Value) -> Value {
if selectedFrameworkIndex != savedFrameworkIndex {
self.doSomethingWith(value: self.frameworks[selectedFrameworkIndex])
}
return val
}
func doSomethingWith(value: String) {
print(value)
}
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: handler(val: $selectedFrameworkIndex), label: Text("Favorite Station")) {
ForEach(0 ..< frameworks.count) {
Text(self.frameworks[$0])
}
}
}
}
.navigationBarTitle("Settings")
}
}
}
Related
Every time I press the button an instance of the School struct is created. I want to show that change on the screen but the button doesn't update. What am I missing to make it work?
import SwiftUI
struct School {
static var studentCount = 0
static func add(student: String) {
print("\(student) joined the school.")
studentCount += 1
}
}
struct ReviewClosuresAndStructs: View {
#State private var test = School.studentCount
var body: some View {
VStack {
Button(action: {
School.add(student: "Tyler")
}, label: {
Text("Press me. \n Students: \(test)")
})
}
}
}
struct ReviewClosuresAndStructs_Preview: PreviewProvider {
static var previews: some View {
ReviewClosuresAndStructs()
}
}
struct School {
var studentCount = 0 // <= here
mutating func add(student: String) { // <= here
print("\(student) joined the school.")
studentCount += 1
}
}
struct ReviewClosuresAndStructs: View {
#State private var test = School() // <= here
var body: some View {
VStack {
Button(action: {
test.add(student: "Tyler")
}, label: {
Text("Press me. \n Students: \(test.studentCount)") // <= here
})
}
}
}
I'm building a picker component and am passing a Binding two layers deep. The issue is that I need to detect it's .onChange on the PickerListView, however, it never gets triggered.
import SwiftUI
public struct SectionItems<T: Hashable>: Hashable {
let header: String
public var items: [T]
public init(header: String, items: [T]) {
self.header = header
self.items = items
}
}
struct PickerListView<T: Hashable>: View {
#Environment(\.dismiss) var dismiss
#Binding var selected: T
#Binding var sections: [SectionItems<T>]
init(selected: Binding<T>, sections: Binding<[SectionItems<T>]>) {
self._selected = selected
self._sections = sections
}
var body: some View {
List {
ForEach(sections, id: \.self) { section in
Section(section.header) {
ForEach(section.items, id: \.self) { item in
Button(action: {
selected = item
dismiss()
}) {
HStack {
Text(String(describing: item))
Spacer()
if selected == item {
Image(systemName: "checkmark")
}
}
}
}
}
}
}
.onChange(of: sections) { _ in
print("PickerListView: \(sections.count)") // Doesn't run
}
}
}
import SwiftUI
public struct PickerView<T: Hashable>: View {
#Binding var selected: T
#Binding var sections: [SectionItems<T>]
public var body: some View {
NavigationView {
NavigationLink(
String(describing: selected),
destination: PickerListView(
selected: $selected,
sections: $sections
)
)
}
.onChange(of: sections) { _ in
print("PickerView: \(sections.count)") // Runs
}
}
}
struct ContentView: View {
#State private var selected = "33"
#State private var sections = [
SectionItems(header: "Itemr", items: [
"33",
"73",
"38"
])
]
var body: some View {
VStack {
PickerView(selected: $selected, sections: $sections)
Button("Add Value", action: {
sections[0].items.append("\(Int.random(in: 1...100))")
})
}
.onChange(of: sections) { _ in
print("ContentView: \(sections.count)") // Runs
}
}
}
You can try a different approach, instead of passing multiple items across many Views you can combine all the variables in a single ObservableObject
Then you can use didSet to make any changes you would make with onChange
///Keeps all the code needed for several views in one spot
class SectionViewModel<T: Hashable>: ObservableObject{
#Published var selected : T
#Published var sections : [SectionItems<T>]{
didSet{
//Use didSet instead of onChange
print("\(type(of: self)) :: \(#function) :: \(sections.count)")
print("\(type(of: self)) :: \(#function) :: section[0]items count: \(sections[0].items.count)")
}
}
init(selected : T, sections : [SectionItems<T>]){
self.selected = selected
self.sections = sections
}
}
Your Views would look something like
struct PickerListView<T: Hashable>: View {
#ObservedObject var vm: SectionViewModel<T>
#Environment(\.dismiss) var dismiss
var body: some View {
List {
ForEach(vm.sections, id: \.self) { section in
Section(section.header) {
ForEach(section.items, id: \.self) { item in
Button(action: {
vm.selected = item
dismiss()
}) {
HStack {
Text(String(describing: item))
Spacer()
if vm.selected == item {
Image(systemName: "checkmark")
}
}
}
}
}
}
}
//Use didset for any changes
// .onChange(of: vm.sections) { _ in
// print("PickerListView: \(vm.sections.count)") // Doesn't run
// }
}
}
public struct PickerView<T: Hashable>: View {
#ObservedObject var vm: SectionViewModel<T>
public var body: some View {
//Navigation View should be at the top
//NavigationView {
NavigationLink(
String(describing: vm.selected),
destination: PickerListView<T>(vm: vm)
)
// }
//Use did set for any actions
// .onChange(of: vm.sections) { _ in
// print("PickerView: \(vm.sections.count)") // Runs
// print("PickerView section[0]items count: \(vm.sections[0].items.count)") // Runs
// }
}
}
struct SampleDeepView: View {
#StateObject var vm: SectionViewModel = .init(selected: "33", sections: [
SectionItems(header: "Itemr", items: [
"33",
"73",
"38"
])
])
var body: some View {
NavigationView{
VStack {
PickerView(vm: vm)
//Adding an item not a section
Button("Add Value", action: {
vm.sections[0].items.append("\(Int.random(in: 1...100))")
})
//Adding a section
Button("Add Section", action: {
vm.sections.append(
SectionItems(header: "Itemr", items: [
"33",
"73",
"38"
])
)
})
}
}
//Use didset for any actions
// .onChange(of: vm.sections) { _ in
// print("ContentView section[0]items count: \(vm.sections[0].items.count)") // Runs
// }
}
}
import Foundation
class Card: Identifiable, ObservableObject {
var id = UUID()
var result: String = ""
init() {
for _ in 1...10 {
let suit = ["D","K","B","S"][Int(arc4random() % 4]
let numStr = ["1","2","3","4"][Int(arc4random() % 4]
result = suit + numStr
}
}
}
import SwiftUI
struct EasyModeView: View {
#ObservedObject var cardModel: Card
var body : some View {
HStack {
ForEach((1...10, id: \.self) { _ in
Button { } label: { Image(cardModel.result) } } }
}
}
I created loop but it always shows me 10 same cards and i want all 10 different.
My Images in Assets are named same as combination of two variables example "D1","D2","S1","S2" etc.
Here is a right way of what you are trying:
struct ContentView: View {
var body: some View {
EasyModeView()
}
}
struct EasyModeView: View {
#StateObject var cardModel: Card = Card()
var body : some View {
HStack {
ForEach(cardModel.results) { card in
Button(action: {}, label: {
Text(card.result)
.padding(3.0)
.background(Color.yellow.cornerRadius(3.0))
})
}
}
Button("re-set") { cardModel.reset() }.padding()
}
}
struct CardType: Identifiable {
let id: UUID = UUID()
var result: String
}
class Card: ObservableObject {
#Published var results: [CardType] = [CardType]()
private let suit: [String] = ["D","K","B","S"]
init() { initializing() }
private func initializing() {
if (results.count > 0) { results.removeAll() }
for _ in 0...9 { results.append(CardType(result: suit.randomElement()! + String(describing: Int.random(in: 1...4)))) }
}
func reset() { initializing() }
}
Result:
You are using only one card, instead of creating 10. Fix like this
struct EasyModeView: View {
var body : some View {
HStack {
ForEach(0..<10) { _ in
Button { } label: {
Image(Card().result)
}
}
}
}
}
I need to pass a parameter calledFrom to a Sheet in SwiftUI.
Strangely, the parameter is not used on the first call, but it works on the following ones.
import SwiftUI
struct ContentView: View {
#State var showSheet = false
#State var calledFrom = -1
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(isPresented: $showSheet) { Dialog(calledFrom: calledFrom) }
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){print("Button \(i) pressed"); calledFrom = i; showSheet = true }
}
}
struct Dialog: View {
var calledFrom : Int
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack{
Text("Called from Button \(calledFrom)")
Button("close"){presentationMode.wrappedValue.dismiss()}
}
.padding()
}
}
You have to use sheet(item:) to get the behavior you're looking for. In iOS 14, the sheet view is calculated before the #State changes:
struct ActiveItem : Identifiable {
var calledFrom: Int
var id: Int { return calledFrom }
}
struct ContentView: View {
#State var activeItem : ActiveItem?
var body: some View {
ForEach((1...4), id: \.self) { i in
getButton(i)
}
.sheet(item: $activeItem) { item in
Dialog(calledFrom: item.calledFrom)
}
.padding()
}
func getButton(_ i : Int) -> some View {
return Button("\(i)"){
print("Button \(i) pressed");
activeItem = ActiveItem(calledFrom: i)
}
}
}
I'm an early bird in programming so I know this question can be ridiculous from the point of view of an expert but I'm stuck in this situation from several days.
I would like to update a row by using a button "Edit" (pencil) after having used another button to store the item with a TextField.
Here's the code:
class Food: Hashable, Codable, Equatable {
var id : UUID = UUID()
var name : String
init(name: String) {
self.name = name
}
static func == (lhs: Food, rhs: Food) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
class Manager: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
#Published var shoppingChart: [Food] = []
init() {
let milk = Food(name: "Milk")
let coffee = Food(name: "Coffee")
shoppingChart.append(milk)
shoppingChart.append(coffee)
}
func newFood(name: String) {
let food = Food(name: name)
shoppingChart.insert(food, at: 0)
}
}
struct ContentView: View {
#ObservedObject var dm : Manager
#State var isAddFoodOpened = false
var body: some View {
VStack {
List {
ForEach(self.dm.shoppingChart, id:\.self) { food in
HStack {
Text(food.name)
Image(systemName: "pencil")
}
}
}
self.buttonAdd
}
}
var buttonAdd: some View {
Button(action: {
self.isAddFoodOpened.toggle()
}) {
Text("Add")
}
.sheet(isPresented: $isAddFoodOpened) {
Add(dm: self.dm, fieldtext: "", isAddFoodOpened: self.$isAddFoodOpened)
}
}
}
struct Add: View {
#ObservedObject var dm : Manager
#State var fieldtext : String = ""
#Binding var isAddFoodOpened : Bool
var body: some View {
VStack {
TextField("Write a food", text: $fieldtext)
buttonSave
}
}
var buttonSave : some View {
Button(action: {
self.dm.newFood(name: self.fieldtext)
self.isAddFoodOpened = false
}) {
Text("Save")
}
}
}
The #ObservedObject var dm : Manager object is never initialized.
Try initialized dm in ContentView like this:
#ObservedObject var dm = Manager()
Ok, so if I understand correctly you want to update/edit a row by using a button "Edit".
This will do it:
struct ContentView: View {
#ObservedObject var dm : Manager
#State var isAddFoodOpened = false
#State var isEditOpened = false
#State var fieldtext : String = ""
var body: some View {
VStack {
List {
ForEach(0..<self.dm.shoppingChart.count, id:\.self) { i in
HStack {
Text(self.dm.shoppingChart[i].name)
Button(action: { self.isEditOpened.toggle() }) {
Image(systemName: "pencil")
}.sheet(isPresented: self.$isEditOpened) {
TextField(self.dm.shoppingChart[i].name, text: self.$fieldtext, onEditingChanged: { _ in
self.dm.shoppingChart[i].name = self.fieldtext
})
}
}
}
}
self.buttonAdd
}
}
var buttonAdd: some View {
Button(action: {
self.isAddFoodOpened.toggle()
}) {
Text("Add")
}
.sheet(isPresented: $isAddFoodOpened) {
Add(dm: self.dm, fieldtext: "", isAddFoodOpened: self.$isAddFoodOpened)
}
}
}