I'm downloading data from Firebase and trying to edit it. It works, but with an issue. I am currently passing data to my EditViewModel with the .onAppear() method of my view. And reading the data from EditViewModel within my view.
class EditViewModel: ObservableObject {
#Published var title: String = ""
}
struct EditView: View {
#State var selected_item: ItemModel
#StateObject var editViewModel = EditViewModel()
var body: some View {
VStack {
TextField("Name of item", text: self.$editViewModel.title)
Divider()
}.onAppear {
DispatchQueue.main.async {
editViewModel.title = selected_item.title
}
}
}
}
I have given you the extremely short-hand version as it's much easier to follow.
However, I push to another view to select options from a list and pop back. As a result, everything is reset due to using the onAppear method. I have spent hours trying to use init() but I am struggling to get my application to even compile, getting errors in the process. I understand it's due to using the .onAppear method, but how can I use init() for this particular view/view-model?
I've search online but I've found the answers to not be useful, or different from what I wish to achieve.
Thank you.
You don't need to use State for input property - it is only for internal view usage. So as far as I understood your scenario, here is a possible solution:
struct EditView: View {
private var selected_item: ItemModel
#StateObject var editViewModel = EditViewModel()
init(selectedItem: ItemModel) {
selected_item = selectedItem
editViewModel.title = selectedItem.title
}
var body: some View {
VStack {
TextField("Name of item", text: self.$editViewModel.title)
Divider()
}.onAppear {
DispatchQueue.main.async {
editViewModel.title = selected_item.title
}
}
}
}
Given the following code
import SwiftUI
struct Foo: View {
var body: some View {
Text("Hello World")
}
}
let anyViewFoo = AnyView(Foo())
debugPrint(anyViewFoo)
Is it possible to check foo for the "real" type or e.g. cast to Foo? So something (which obviously doesn't work) like the following would be awesome:
let foo: Foo? = anyViewFoo as? Foo
Does something like this exist?
SwiftUI AnyView does not give access to original view, but we can use our custom to store it and then access.
Note: pay attention - all views are structs, so accessing original view you get a copy (as everywhere in SwiftUI)
Here is a possible approach:
struct AnyMyView: View {
private let internalView: AnyView
let originalView: Any
init<V: View>(_ view: V) {
internalView = AnyView(view)
originalView = view
}
var body: some View {
internalView
}
}
and now a demo of usage based on your original code
struct DemoAnyMyView: View {
let anyViewFoo = AnyMyView(Foo())
var body: some View {
VStack {
// erased type view
anyViewFoo
Divider()
// restored type view
if let foo = anyViewFoo.originalView as? Foo {
foo
}
}
}
}
Prepared & tested with Xcode 12.1 / iOS 14.1
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
I'm having a problem trying to get textfields working in SwiftUI.
I get Fatal error: Accessing State> outside View.body whenever I try to run the following code.
Anyone have a suggestion?
struct SearchRoot : View {
#State var text: String = ""
var body: some View {
HStack {
TextField($text,
placeholder: Text("type something here..."))
Button(action: {
// Closure will be called once user taps your button
print(self.$text)
}) {
Text("SEND")
}
}
}
}
I'm running Xcode Version 11.0 beta (11M336w) on macOS 10.15 Beta (19A471t)
Edit: Simplified code, still getting the same error.
struct SearchRoot : View {
#State var text: String = ""
var body: some View {
TextField($text,
placeholder: Text("type something here..."))
}
}
The compiler emits an error if the $ operator is used outside body, in a View.
The button initializer is defined as:
init(action: #escaping () -> Void, #ViewBuilder label: () -> Label)
You're using $ in an escaping closure, in the first snippet of code.
That means the action may outlive (escape) the body, hence the error.
The second snippet compiles and works fine for me.
Eureka! SwiftUI wants a single source of truth.
What I neglected to include in my original code snippets is that this struct is within a tabbed application.
To fix this I needed to define the #State var text: String = "" in the struct that creates the top level TabbedView, then use $Binding in the SearchRoot.
I'm not sure if this is works as designed or just a beta 1 issue, but it's the way it works for now.
struct ContentView : View {
#State private var selection = 0
#State private var text: String = "searching ex"
var body: some View {
TabbedView(selection: $selection){
ShoppingListRoot().body.tabItemLabel(Text("Cart")).tag(0)
SearchRoot(text: $text).body.tabItemLabel(Text("Search")).tag(1)
StoreRoot().body.tabItemLabel(Text("Store")).tag(2)
BudgetRoot().body
.tabItemLabel(Text("Budget"))
.tag(3)
SettingsRoot().body
.tabItemLabel(Text("Settings"))
.tag(4)
}
}
}
So I tried to put a print statement while debugging in a SwiftUI View.
print("landmark: \(landmark)")
In the following body.
var body: some View {
NavigationView {
List {
Toggle(isOn: $userData.showFavoritesOnly) {
Text("Favorite only")
}
ForEach(landmarkData) { landmark in
print("landmark: \(landmark)")
if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
Compiler errors out:
So, what is the proper way to print to console in SwiftUI?
EDIT:
I made Landmark conform to CustomStringConvertible:
struct Landmark: Hashable, Codable, Identifiable, CustomStringConvertible {
var description: String { name+"\(id)" }
var id: Int
var name: String
.....
I still get the "String is not convertible to any" error. Should it work now?
You can easily add a print statement anywhere in a function builder by simply storing its return value in a wildcard, effectively ignoring it:
let _ = print("hi!")
No setup or other verbosity needed!
Why does this work while a regular print() doesn't?
The way SwiftUI's #ViewBuilder (and result builders in general) is that they consume any values in a closure that aren't used otherwise (e.g. if you just have 42 on its own line). The print function returns Void (nothing), which the builder would have to build into a view, so it fails. By instead assigning it to a variable (in this case _, basically a variable that you can never access), the Void is never offered to the view builder in the first place.
You could argue the builder should simply accept and ignore Void values, but the idea is that your builder closures should not have side effects (I'd remove print statements after finishing debugging too)—you should not rely on these closures being called at certain times.
Here's a helper Print( ... ) View that acts like a print( ... ) function but within a View
Put this in any of your view files
extension View {
func Print(_ vars: Any...) -> some View {
for v in vars { print(v) }
return EmptyView()
}
}
and use inside of body like so
Print("Here I am", varOne, varTwo ...)
or inside a ForEach {} like so
self.Print("Inside ForEach", varOne, varTwo ...)
Note: you might need to put Print() into a Group {} when combining with existing views
Try right-clicking on the live preview play button and selecting 'Debug Preview from the popup
You can print in the body structure but to do so you have to explicitly return the view you want to render. The body property inside a View is just a computed property like any other in Swift that implicitly returns the view. And just like any other computed property, you can perform operations inside the computed property as long as a value is explicitly returned. For example, this will throw an error when you try to print because there is no explicit return:
struct SomeView: View {
#State var isOpen = false
var body: some View {
print(isOpen) // error thrown here
VStack {
// other view code
}
}
}
But if we explicitly return the view we want then it will work e.g.
struct SomeView: View {
#State var isOpen = false
var body: some View {
print(isOpen) // this ok because we explicitly returned the view below
// Notice the added 'return' below
return VStack {
// other view code
}
}
}
The above will work well if you're looking to view how state or environment objects are changing before returning your view, but if you want to print something deeper down within the view you are trying to return, then I would go with #Rok Krulec answer.
It is possible to use print() remembering that all SwiftUI View content are (a) implicit closures and (b) it is highly recommended to decompose views as much as possible to have simple structure, so it might look like the following...
struct Model: Identifiable {
let value: String
var id: String {
value
}
init (_ value: String) {
self.value = value
}
}
struct TestView: View {
#State var showFavoritesOnly = false
#State var listData: [Model] = [Model("one"), Model("two"), Model("three")]
var body: some View {
NavigationView {
List {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorite only")
}
ForEach(listData) { data in
self.rowView(data: data)
}
}
}
}
private func rowView(data: Model) -> some View {
#if DEBUG
print(">> \(data.value)")
#endif
return NavigationLink(destination: Text("Details")) {
Text("Go next from \(data.value)")
}
}
}
... and right clicking in Preview to select run as Debug Preview we get:
2019-10-31 14:28:03.467635+0200 Test[65344:11155167] [Agent] Received connection, creating agent
2019-10-31 14:28:04.472314+0200 Test[65344:11155168] [Agent] Received display message
>> one
>> two
>> three
You can declare a printing() method that includes print() and returns EmptyView struct.
struct ContentView: View {
#State private var offset = CGSize.zero
func printing(_ items: Any...) -> some View {
let _ = print(items)
return EmptyView()
}
var body: some View {
#if DEBUG
printing(offset) // prints [(0.0, 0.0)]
#endif
ZStack {
Text("Hello")
}
}
}
The safest and easiest way to print while debugging in a SwiftUI View.
extension View {
func Print(_ item: Any) -> some View {
#if DEBUG
print(item)
#endif
return self
}
}
Usage Example:
struct ContentView: View {
var body: some View {
VStack {
ForEach((1...5), id: \.self) { number in
Text("\(number)")
.Print(number)
}
}
}
}
Console output:
1
2
3
4
5
It can be generalized to:
extension View {
func Perform(_ block: () -> Void) -> some View {
block()
return EmptyView()
}
}
So in your example:
ForEach(landmarkData) { landmark in
Perform { print("landmark: \(landmark)") }
if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
Here you go. It will just work like simple print but inside a view.
func printv( _ data : Any)-> EmptyView{
print(data)
return EmptyView()
}
and use it like that
struct ContentView: View {
var body: some View {
VStack() {
Text("hello To SwiftUI")
printv("its easy to code in SwiftUI")
Text("And Good to have you here")
}
}
}
The following extension on View is as intuitive as print because it's made to replicate the default print(_:separator:terminator:) function signature & behavior.
extension View {
func printUI(_ args: Any..., separator: String = " ", terminator: String = "\n") -> EmptyView {
let output = args.map(String.init(describing:)).joined(separator: separator)
print(output, terminator: terminator)
return EmptyView()
}
}
Usage Example:
struct ContentView: View {
var body: some View {
VStack {
printUI("ContentView", "1")
printUI("ContentView", "2", separator: ", ", terminator: "\n.\n.\n")
printUI("ContentView", "3", separator: "; ")
Text("Hello, World!")
}
}
}
Console Output:
ContentView 1
ContentView, 2
.
.
ContentView; 3
EDIT: Debug Preview is no longer supported in the latest versions of Xcode.
Very easy way to debug your Preview:
Open your Swift project in Xcode 11.
Right-click (or Control-click) on the Live Preview button in the bottom right corner of the preview.
Select Debug Preview.
How to debug your SwiftUI previews in Xcode
// Try this, add a 'return' on a view then the 'print' can stay alive in.
struct ContentView: View {
var num: Int = 1
var body: some View {
print(num)
return Text("hello")
}
}
You can't because you're in a computed property. You need for example a button and in the action you define the print. Or work with breakpoints
You can not print in body structure i.e. a structure which is some view type.For print you need to make function out of body structure and call it using button or something else.
This should work
if true {
print(aVar, "xx")
}
return ZStack {
...
}