What is the good format for temperature in SwiftUI TextField? - swiftui

I need to create a TextField with input value for temperature that the user wants to convert.
The code:
import SwiftUI
import Combine
private final class TempConverterViewState: ObservableObject {
#Published var temperatureInput = Measurement(value: 0, unit: UnitTemperature.celsius) }
struct TempConverterView: View {
#StateObject private var state = TempConverterViewState()
var body: some View {
Form {
Section {
TextField("Value", value: $state.temperatureInput, format: .measurement(width: Measurement<UnitTemperature>, numberFormatStyle: Decimal))
}
}
}
}
Neither initialization, nor calling the variable throw any errors. The error is thrown in var body: some View line and it reads:
Failed to produce diagnostic for expression; please submit a bug report
Do you perhaps see what's wrong with my code?

Related

SwiftUI view get views in the body or define view as property

I am trying to get the view inside the body for observing purpose, but looking for different ways the view inside the body can be accessed.
struct ContentView: View {
#State var userNameText: String
var body: some View {
startObservingInput()
return TextField("hello", text: $userNameText)
}
func startObservingInput() {
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
Option 1 is simple, where we pass the TextField view.
Option 2 is something I am looking for, if we can get any view inside the hierarchy. In this case Text Field.
Option 3, Tried to create a property but I get the following error.
ex:
struct ContentView: View {
#State var userNameText: String
var textField = TextField("hello", text: $userNameText)
......
}
Cannot use instance member '$userNameText' within property initializer; property initializers run before 'self' is available
SwiftUI is different from what you're probably used to. Unlike UIKit, you don't "store" views in properties. There's no delegates either.
Instead, you directly pass in a property — userNameText — that will be linked to the text field's text. Since this updates itself automatically, you can use the onChange modifier to observe changes.
struct ContentView: View {
#State var userNameText: String
var body: some View {
TextField("hello", text: $userNameText)
.onChange(of: userNameText) { newValue in
print("Text changed to: \(newValue)")
}
}
}
Here is what I did, look at it:
//
// ViewProp.swift
// SwiftDemo1
//
// Created by CreoleMacbookPro on 12/19/22.
//
import SwiftUI
struct ViewProp: View {
#State var userNameText: String = " "
var body: some View {
let textField: TextField<Text> = TextField("hello", text: $userNameText)
let simpleText: Text = Text("Hello, World!")
let _ = print(type(of: textField))
startObservingInput(textField: textField)
Button {
userNameText = "Jatin Bhuva"
} label: {
Text("Press Me..")
}
// textField
}
func startObservingInput(textField: TextField<Text>) -> some View {
textField
// How do we get TextField instance here.
// Option 1 - Pass as parmeter here.
// Option 2 - Is there a way to get view from body ex: self.body.textFieldView
// Option 3 - can create as a property in CotentView but the text binding refers to self which will not be allowed before its initalized so that will fail to compile
//var textField = TextField("hello", text: $userNameText)
}
}
struct ViewProp_Previews: PreviewProvider {
static var previews: some View {
ViewProp()
}
}

'self' used before all stored properties are initialized error - #State - extension - second file

I want to decouple some views. For this case I created an initializer for a view with a custom argument in an extension in a second file. Unfortunately I get this message: "'self' used before all stored properties are initialized".
If I put view and extension in the same file, it works. But in this case, the view file needs to know the custom argument. This may not be needed in another project.
For simplicity, I have created a sample code that shows the problem. How can I initialize the #State property name in the extension initializer if the extension is in another file?
// ListCell.swift
import SwiftUI
struct ListCell: View {
#State var name: String
var count: Int
var body: some View {
HStack {
Text(name)
Text(": \(count)")
}
}
}
// ListCell+Ext.swift
import SwiftUI
extension ListCell {
init(name: String) {
self.name = name
count = 0
}
}
The problem is that #State var name: String is actually sugar for a private value _name with some extra logic. You are trying to set String to the value that is exposed from the state (which is State<String>). If you will try to set _name in the extension the compiler will complain that you can't access this private value from the extension.
Seems like you can create a convenience init in your extension, while keeping the default one, and just using it:
// ListCell.swift
struct ListCell: View {
#State var name: String
var count: Int
var body: some View {
HStack {
Text(name)
Text(": \(count)")
}
}
}
// ListCell+Ext.swift
import SwiftUI
extension ListCell {
init(name: String) {
self.init(name: name, count: 0)
}
}
Note that Swift docs do say that a default struct initializer is only generated when there isn't a custom one ("Structure types automatically receive a memberwise initializer if they don’t define any of their own custom initializers" - under Memberwise Initializers) but seems like when using an extension like in your case the default init is still generated, and you can use it instead of creating one...
By the way, in general, #State should be used when your view owns and creates the value, if it is passed from above, it's likely that you should actually use #Binding instead.
Here the solution with #binding:
// ListCell.swift
import SwiftUI
struct ListCell: View {
#Binding var name: String
var count: Int
var body: some View {
HStack {
Text(name)
Text(": \(count)")
}
}
}
// ListCell+Ext.swift
import SwiftUI
extension ListCell {
init(name: Binding<String>) {
self.init(name: name, count: 0)
}
}

How can SwiftUI Views change if the updated variable isn’t marked with a $?

In many cases in SwiftUI, values are marked with a $ to indicate that they’re a Binding variable, and allow them to update dynamically. Here’s an example of this behavior:
class Car: ObservableObject {
#Published var isReadyForSale = true
}
struct SaleView: View {
#Binding var isOn: Bool
var body: some View {
Toggle("Ready for Sale", isOn: $isOn)
}
}
struct ContentView: View {
#ObservedObject var car: Car
var body: some View {
Text("Details")
.font(.headline)
SaleView(isOn: $car.isReadyForSale) // generates a Binding to 'isReadyForSale' property
}
}
The $ is used twice to allow the Toggle to change whether the car is ready for sale, and for the car’s status to update the Toggle.
However, some values seem to update without the $. For instance, in this tutorial about different property wrappers, they show the following example:
class TestObject: ObservableObject {
#Published var num: Int = 0
}
struct ContentView: View {
#StateObject var stateObject = TestObject()
var body: some View {
VStack {
Text("State object: \(stateObject.num)")
Button("Increase state object", action: {
stateObject.num += 1
print("State object: \(stateObject.num)")
})
}
.onChange(of: stateObject.num) { newStateObject in
print("State: \(newStateObject)")
}
}
}
Why does it use Text("State object: \(stateObject.num)") and not Text("State object: \($stateObject.num)") with a dollar sign prefix? It was my understanding when you wanted a view to automatically update when a variable it uses changes, you prefix it with a dollar sign. Is that wrong?

crashing playground with ForEach

I have this code, see below, it contains a ForEach loop which is commented out. the same view in an App runs just fine even if the ForEach loop is enabled. However, in a playground it crashes with a very unhelpful error message:
"error: Playground execution aborted: error: Execution was interrupted, reason: signal SIGABRT.
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation."
I tried finding information about this message. From what I understand it means that lldb does not know exactly what goes wrong and prints this. So, I turn to stack overflow in the hope someone does know what exactly might by going wrong here....?
import Cocoa
import SwiftUI
import PlaygroundSupport
import Combine
struct RowModel : Identifiable, Hashable {
var text : String
var id : UUID = UUID()
}
class My : ObservableObject {
#Published var s: String = "Hi there"
#Published var elements = [
RowModel(text: "een"),
RowModel(text: "twee"),
RowModel(text: "drie"),
RowModel(text: "vier"),
]
}
struct Row : View {
var item : String
var body : some View {
Text(item)
}
}
struct Screen : View {
#StateObject var my = My()
var body: some View {
VStack {
Text("The screen")
VStack {
Row(item: my.elements[0].text)
Row(item: my.elements[1].text)
// ForEach(0 ..< my.elements.count, id: \.self){ (index : Int) in
//
// Row(item: my.elements[index].text)
// }
}.frame(height: 100)
TextField("enter values", text: $my.s)
}
}
}
var view = Screen()
PlaygroundPage.current.setLiveView(view)
I'm late to the party, but this bug is still not fixed at this point so I figured I'd leave this for any future viewer.
This seems to be an issue with results bar on the right, because moving the view to a separate file in Sources (press Command 0) works fine.
there is a workaround for this bug
move ContentView to a separate file in Sources directory
make your models public
more info
https://stackoverflow.com/a/67120969/2027018

Is there a way decouple views from view models like the following?

My target is 2 thing:
1. to make a view depending on a view model protocol not a concrete class.
2. a sub view gets the view model from the environment instead of passing it through the view hierarchy
I've mentioned my goals so if there's a totally different way to achieve them, I'm open to suggestion.
Here's what've tried and failed of course and raised weird error:
struct ContentView: View {
var body: some View {
NavigationView {
MyView()
}
}
}
struct MyView: View {
#EnvironmentObject var viewModel: some ViewModelProtocol
var body: some View {
HStack {
TextField("Enter something...", text:$viewModel.text)
Text(viewModel.greetings)
}
}
}
//MARK:- View Model
protocol ViewModelProtocol: ObservableObject {
var greetings: String { get }
var text: String { get set }
}
class ConcreteViewModel: ViewModelProtocol {
var greetings: String { "Hello everyone..!" }
#Published var text = ""
}
//MARK:- Usage
let parent = ContentView().environmentObject(ConcreteViewModel())
Yes there is, but it's not very pretty.
You're running into issues, since the compiler can't understand how it's ever supposed to infer what type that that some protocol should be.
The reason why some works in declaring your view, is that it's inferred from the type of whatever you supply to it.
If you make your view struct take a generic viewmodel type, then you can get this up and compiling.
struct MyView<ViewModel: ViewModelProtocol>: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
Text(viewModel.greetings)
}
}
the bummer here, is that you now have to declare the type of viewmodel whenever you use this view, like so:
let test: MyView<ConcreteViewModel> = MyView()