Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I'm learning how to code using the tutorial provided by Apple. I'm at the stage where I learn to pass data with bindings.
I'm still learning so please expect two noob questions below, I've tried to find answers to this but couldn't find anything compelling, hence this post. Thanks in advance for your patience :)
Summarize the problem
The example provided in the tutorial makes use of TextField to allow user inputs.
I'm trying to do that so that people can enter a year (2008) for example, however, that doesn't work because the data I'm trying to pass it to is a Double, so I get an error message.
I've tried to use the date picker as well, but that leads me to deal with dates, which seems very complex to me at this stage.
Describe what you’ve tried
I've tried to make it so that the user input from the TextField is considered as a Double, but every way I tried it leads to more error messages. Nothing has worked so far.
When appropriate, show some code:
My data model:
struct Animal: Identifiable {
let id: UUID
var name: String
var birthyear: Int
}
init(id: UUID = UUID(), name: String, birthyear: Int){
self.id = id
self.name = name
self.birthyear = birthyear
}
extension Animal {
static var data: [Animal] {
[
Animal(name: "Félix", birthyear: 1999
]
}
}
extension Animal {
struct Data {
var name: String = ""
var birthyear: Double = 1999
}
var data: Data {
return Data (name: name, birthyear: Double(birthyear))
}
mutating func update(from data: Data) {
name = data.name
birthyear = Int(data.birthyear)
}
}
and the code where my TextField appears (the EditView):
import SwiftUI
struct EditView: View {
#Binding var animData: Animal.Data
var body: some View {
Form {
Section(header: Text("Animal Info")){
List {
TextField("Title", text: $animData.name)
HStack {
TextField("Birth Year", text: $animData.vaccinyear)
}
}
}
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView(animData: .constant(Animal.data[0].data))
}
}
Now I have two questions:
If I want the data passed as a Double, so my code above would actually work, what should I do? It works with a slider, but it is a ridiculous way to go about it.
(the slider code that works)
var body: some View {
Form {
Section(header: Text("Animal Info")){
List {
TextField("Title", text: $animData.name)
HStack {
Slider(value: $animData.birthyear, in: 1999...2021, step: 1.0) {
Text("Length")
}
}
}
}
edit: solution from here: SwiftUI - How to create TextField that only accepts numbers
TextField("Birth Year", text: $animData.birthyear)
.keyboardType(.numberPad)
.onReceive(Just(animData.birthyear)) { newValue in
let filtered = newValue.filter { "0123456789".contains($0) }
if filtered != newValue {
self.animData.birthyear = filtered
}
}
If you're interested in a solution that still lets you keep an Int, this modification works by using an intermediary #State variable with the String value and then assigns the real Int value if it is valid.
struct ContentView : View {
#State var birthyear = 1999
#State var stringBirthyear = ""
var birthyearBinding : Binding<String> {
.init {
print("returning",birthyear)
return "\(birthyear)"
} set: { (newValue) in
if let intVal = Int(newValue) {
DispatchQueue.main.async {
print("Setting birthyear to",intVal)
self.birthyear = intVal
}
}
}
}
var body: some View {
Text("\(birthyear)")
TextField("Birth Year", text: $stringBirthyear)
.keyboardType(.numberPad)
.onReceive(Just(stringBirthyear)) { newValue in
let filtered = newValue.filter { "0123456789".contains($0) }
if filtered != newValue {
print(filtered,newValue)
stringBirthyear = filtered
}
if let intVal = Int(filtered) {
birthyear = intVal
}
}
.onAppear {
stringBirthyear = "\(birthyear)"
}
}
}
Related
I'm currently trying to change the data the picker will display based on the value in the series text field. I'm not getting the picker to show up, I'm not getting any errors but I'm getting this warning "Non-constant range: not an integer range" for both the ForEach lines below.
struct ConveyorTracks: View {
#State private var series = ""
#State private var selectedMaterial = 0
#State private var selectedWidth = 0
#State private var positRack = false
let materials8500 = ["HP", "LF", "Steel"]
let widths8500 = ["3.25", "4", "6"]
let materials882 = ["HP", "LF", "PS", "PSX"]
let widths882 = ["3.25", "4.5", "6","7.5", "10", "12"]
var materials: [String] {
if series == "8500" {
return materials8500
} else if series == "882" {
return materials882
} else {
return []
}
}
var widths: [String] {
if series == "8500" {
return widths8500
} else if series == "882" {
return widths882
} else {
return []
}
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Text("Series:")
TextField("Enter series", text: $series)
}.padding()
HStack {
Text("Material:")
Picker("Materials", selection: $selectedMaterial) {
ForEach(materials.indices) { index in
Text(self.materials[index])
}
}.pickerStyle(SegmentedPickerStyle())
}.padding()
HStack {
Text("Width:")
Picker("Widths", selection: $selectedWidth) {
ForEach(widths.indices) { index in
Text(self.widths[index])
}
}.pickerStyle(SegmentedPickerStyle())
}.padding()
HStack {
Text("Positive Rack:")
Toggle("", isOn: $positRack)
}.padding()
}
}
}
struct ConveyorTrack_Previews: PreviewProvider {
static var previews: some View {
ConveyorTracks()
}
}
I would like the pickers to change based on which value is input in the series text field, for both materials and width.
Perhaps pickers isn't the best choice, I am open to any suggestions.
Thanks!
ForEach(materials.indices)
Needs to be
ForEach(materials.indices, id: \.self)
Because you are not using a compile-time constant in ForEach.
In general for fixed selections like this your code can be much simpler if you make everything enums, and make the enums Identifiable. This simplified example only shows one set of materials but you could return an array of applicable materials depending on the selected series (which could also be an enum?)
enum Material: Identifiable, CaseIterable {
case hp, lf, steel
var id: Material { self }
var title: String {
... return an appropriate title
}
}
#State var material: Material
...
Picker("Material", selection: $material) {
ForEach(Material.allCases) {
Text($0.title)
}
}
My app uses TextFields everywhere to modify CoreData entities' String attributes. They work very poorly - typing a space or getting an auto correct event seems to make the cursor jump to the end of the window. Keystrokes are missed and the whole experience is laggy. TextEditors, on the other hand, work fine. The behavior doesn't appear on the simulator, only on (multiple) real devices.
What am I doing wrong here? Am I using TextFields wrong?
Code is below, it's basically the starter Xcode app with a "text: String?" attribute added to the "item" CoreData entity.
struct Detail: View {
#ObservedObject var item: Item
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $item.text ?? "")
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $item.text ?? "")
}, header: {
Text("TextEditor")
})
}
}
}
}
// Optional binding used
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
Update:
I ended up just putting the TextFields into a subview and then writing their value back to the NSManagedObject via a binding every time the value changes.
I have no idea why, but this fixes the problem for me.
struct CustomTextField: View {
#Binding var string: String?
#State var localString: String
let prompt: String
init(string: Binding<String?>, prompt: String) {
_string = string
_localString = State(initialValue: string.wrappedValue ?? "")
self.prompt = prompt
}
var body: some View {
TextField(prompt, text: $localString, axis: .vertical)
.onChange(of: localString, perform: { _ in
string = localString
})
}
}
Example of using onSubmit, which does not cause CoreData to save the data on every input by the keyboard.
struct Detail: View {
#ObservedObject var item: Item
#State var text: String = "" // for starting with an empty textfield
// Alternative if you want the data from item:
// #State var text: String = item.text.wrappedValue // This only works if text is a binding.
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextEditor")
})
}
}
}
}
If that does not help, it would be nice to know how Item looks like.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I'm trying to show a list of data from an array inside a list but is not working and i'm getting this error below
There is any other way to display this data from the array to the list?
Thanks :-)
The error:
Value of type 'Any' has no member 'title'
My code:
import SwiftUI
import FirebaseAuth
import FirebaseDatabase
import Foundation
struct goal: Identifiable {
var id: String
var description: String
var endDate: String
var category: String
}
struct ActiveGoalsView: View {
#State var goals = []
#State var ref = Database.database().reference()
#State private var multiSelection = Set<UUID>()
func getData() {
ref.child("users").child(Auth.auth().currentUser?.uid ?? "noid").child("goals").observeSingleEvent(of: .value) { snapshot in
for snap in snapshot.children {
let snap1 = snap as! DataSnapshot
let goalId = snap1.childSnapshot(forPath: "goalId").value
let description = snap1.childSnapshot(forPath: "description").value
let endDate = snap1.childSnapshot(forPath: "end_date").value
let category = snap1.childSnapshot(forPath: "category").value
goals.append(goal(id: goalId as! String, description: description as! String, endDate: endDate as! String, category: category as! String))
//print(snap1.childSnapshot(forPath: "goalId").value)
//print(snap)
print(goals)
}
}
}
var body: some View {
NavigationView {
ForEach(goals, id: \.self) {goal in
Text(goal.title)
}
List() {
Button(action: {getData()}, label: {
Text("Button")
})
/*
ForEach(goals, id: \.self) {goals in
HStack {
Button(action: {}, label: {
Text(goals)
})
}
}*/
}.navigationBarHidden(true)
}
}
}
struct ActiveGoalsView_Previews: PreviewProvider {
static var previews: some View {
ActiveGoalsView()
}
}
You didn't define what type goals should contain -- you just used the [], which implies Any.
#State var goals = []
So, replace that with this:
#State var goals: [goal] = []
Now that Swift knows that goals is an array of goal (which conforms to Identifiable), you can remove the , id: \.self in your ForEach. Also, Text(goal.title) doesn't make sense because goal doesn't have a property called title. Maybe you meant description?
Note: You should capitalize structs like goal -> Goal.
Note 2: Text(goal) also doesn't make sense. You probably meant Text(goal.description).
I am making a personal project to study SwiftUI. All was going well, the I noticed a bug on my app.
I have the simple view bellow, that saves a description, a value and some tags on my ViewModel. I am having an issue with the $viewModel.value. That variable is not being filled with values from the view.
I supose that my #Published var value: Double? from my ViewModel should be updated whenever the user types some value. Thing is, it is not updating on any iPhone 11 and up, but it works perfectly on the iPhone 8.
public struct AddBillView: View {
#ObservedObject private var viewModel: AddBillViewModel
#Environment(\.presentationMode) var presentationMode
public let onExpenseCreated: ((_ expense: Expense)->Void)
public var body: some View {
Text("Add Expense")
VStack {
TextField("Descrição", text: $viewModel.name)
HStack {
Text("Valor \(NumberFormatter.currency.currencySymbol)")
CurrencyTextField("Value", value: $viewModel.value)
.multilineTextAlignment(TextAlignment.leading)
}
HStack {
Text("Tags")
TextField("car pets home",
text: $viewModel.tags)
}
Picker("Type", selection: $viewModel.type) {
Text("Paid").tag("Paid")
Text("Unpaid").tag("Unpaid")
Text("Credit").tag("Credit")
}
}.navigationTitle("+ Expense")
Button("Adicionar") {
if !viewModel.hasExpense() {
return
}
onExpenseCreated(viewModel.expense())
self.presentationMode.wrappedValue.dismiss()
}
}
public init(viewModel outViewModel: AddBillViewModel,
onExpenseCreated: #escaping ((_ expense: Expense)->Void)) {
self.viewModel = outViewModel
self.onExpenseCreated = onExpenseCreated
}
}
And I have a ViewModel:
public class AddBillViewModel: ObservableObject {
#Published var name: String = ""
#Published var type: String = "Paid"
#Published var tags: String = ""
#Published var value: Double?
init(expense: Expense?=nil) {
self.name = expense?.name ?? ""
self.type = expense?.type.rawValue ?? "Paid"
self.tags = expense?.tags?.map { String($0.name) }.joined(separator: " ") ?? ""
self.value = expense?.value
}
func hasExpense() -> Bool {
if self.name.isEmpty ||
self.value == nil ||
self.value?.isZero == true {
return false
}
return true
}
func expense() -> Expense {
let tags = self.tags.split(separator: " ").map { Tag(name: String($0)) }
return Expense(name: self.name, value: self.value ?? 0.0 ,
type: ExpenseType(rawValue: self.type)!,
id: UUID().uuidString,
tags: tags)
}
}
Then I use my view:
AddBillView(viewModel: AddBillViewModel()) { expense in
viewModel.add(expense: expense)
viewModel.state = .idle
}
I already google it and spend a couple of hours looking for an answer, with no luck. Someone have any ideas?
Edited
Here is the code for the CurrencyTextField. I`m using this component:
https://github.com/youjinp/SwiftUIKit/blob/master/Sources/SwiftUIKit/views/CurrencyTextField.swift
But the component works perfectly fine on iPhone 8 simulator and with a #State property inside my view. It does not work only with my ViewModel
I figured it out! The problem was that my AddBillViewModel is an ObservableObject and I was marking each property with #Published. This was causing some kind of double observable object.
I removed the #Published and it started working again.
I have dictionary that saves a questions as keys and answers as values ,I want change the answers using TextField each question to different answer and save them using UserDefaults .
I tried different ways but I had some issues , like all the values of the dictionary
would be saved to the same sting , or..it would show an error index out of range when I try to access dictionary.values.sorted()[Int].
1- created questions array
struct Questions :ObservableObject{
#Published var questions = [ “Q1” , “Q2” , “Q3” ]
}
2- here's the list of questions(I want the answers to be shown below the question)
struct FavoritesQuestionsListView:View {
#ObservedObject var questions = Questions()
#ObservedObject var dictionary = Dictionary()
#State var isPresented: Bool = false
var body: some View {
VStack{
List(0..<self.questions.count , id:\.self){ currentQuestion in
VStack{
Text(questions[currentQuestions])
// I want to show here the saved answer to each questions but here’s what I tried…..
//Text(saveAnswers()) -> Error :index out of range.
//Text(dictionary.dictionary[questions.questions[currentQuestion] ?? "")
//only show the last answer I answered, and when I used print I found that all questions
//are saved to the same answer.
}
}.sheet(isPresented: self.$isPresented, content: {
QuestionsSheet( currentQuestion: currentQuestion , dictionary :dictionary ,isPresented:
self.$isPresented ,questions :questions)
})
.onTapGesture{}
self.isPresented.toggle()
}
func saveAnswers()->String{
let values = Array(dictionary.dictionary.values.sorted())
return values[currentQuestion]
}
}
}
3- the question sheet
struct QuestionsSheet:View {
#ObservedObject var questions: Questions
#ObservedObject var dictionary: Dictionary
#State var currentQuestion: Int = 0
#Binding var isPresented: Bool
#State var answer: String = ""
var body: some View {
VStack{
Text("\(currentQuestion + 1)/ \(questions.questions.count)")
Text(self.questions.questions[currentQuestion])
TextField("Enter your Answer..", text: self.$answer)
Button {
self.nextQuestion()
self.saveAnswers()
} label: {
Text("Next")
}
}
}
func nextQuestion(){
if self.question <= (self.favoritesQuestionsData.favoritesQuestionsJson[category].favoritesQuestions.count - 1) {
self.question += 1
}
}
func saveAnswers() {
//HERE’S My PROBLEM
//All the answers of the answers are saved to the same value
dictionary.dictionary = [questions.questions[currentQuestion] : answer]
//using updateValue to be able to change the answer in the future
dictionary.dictionary.updateValue(answer, forKey:questions.questions[currentQuestion])
}
}
4- finally the dictionary..
//it works successfully
class Dictionary: ObservableObject {
#Published var dictionary = UserDefaults.standard.object(forKey: “answer”) as?
[String:String] ?? [:]{
didSet{
UserDefaults.standard.set(dictionary, forKey: “answer”)
}
}
}