How to print() to Xcode console in SwiftUI?
Asked Answered
K

14

140

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: Xcode compiler error

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?

Kimberykimble answered 9/6, 2019 at 19:35 Comment(5)
Does your landmark conform to CustomStringConvertible?Glasswort
Your question is about printing but you can't compile since you have an error. Fix the error first and I'm sure the print will work fine.Cromagnon
I edited the question. For some reason I had to clean and build again, then the other error when away.Kimberykimble
Have you tried to add return before NavigationButton?Marniemaro
On Xcode 12.4 all prints end up in the debug area only when I use a real device.Zachariahzacharias
M
258

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.

Manhattan answered 30/7, 2020 at 22:46 Comment(7)
Geez why must some things be so weird in SwiftUI, so many things feel like "workarounds" or "hacks" just to reach a simple result.Maniac
An extremely simple solution for a weird problem - thanks!Kurtzig
@Maniac I'm learning SwiftUI and I kinda agree with you. But at the same time, restrictions can result in more structure. That is: everything within the body is to be of type View or something with a compiler directive that makes it known it's to be ignored.Cimon
@Cimon Reading my old comment from multiple months ago is kind of funny, now that I actually got a really good grip on SwiftUI. I agree with you. I think the problem is that the answer by juliand didn't mention the reason why it wouldn't work and lets people believe this is how it's done for no reason. Also, Xcode's error messages could also be more clear to explain why it's not possible :)Maniac
@Maniac Good thoughts! I've expanded on my answer with an explanation of why it works and some thoughts on why view builders work this way.Manhattan
I wanted to put a NSLog statement in the body property expecting a WidgetConfiguration. The above answer didn't work. When I explicitly added a return statement in the code (as mentioned in @Darkisa's answer), then NSLog worked...without having the LHS.Cloris
@Cloris Unlike View.body, Widget.body is not annotated with any kind of result builder (the magic behind SwiftUI), so you don't need any kind of special magic to support print statements and whatnot. The standard rules of Swift still apply just the same, so you have to write return explicitly if your body consists of multiple statements/expressions.Manhattan
H
67

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

Harbour answered 24/12, 2019 at 22:2 Comment(2)
This modification lets you use "print" syntax: func Print(_ items: Any..., separator: String = " ", terminator: String = "\n") -> some View { let string = items.map{String(describing: $0)}.joined(separator: separator) print("Print:", string, terminator: terminator) // could add other stuff here return EmptyView() } self.Print("view named", self.someName, "number:", self.num)Malda
Version with NSLog for anyone wanting timestamps: extension View { func NSLogView(_ format: String, _ args: CVarArg...) -> some View { NSLog(format, args) return EmptyView() } } Pothook
E
37

Try right-clicking on the live preview play button and selecting 'Debug Preview from the popup

Elaboration answered 30/8, 2019 at 7:14 Comment(2)
About Debug Preview for SwiftUI in macOS, It seems that the latest Xcode (11.3) hasn't supported to print a log to Xcode's console with print() yet. AFAIK only a breakpoint with a log message action can do that for now. Or, as another workaround, you can use Debug Preview with Console.app so that you can see the debug log in the app. Please note that you'd need to use NSLog(), since print() doesn't work for output to Console.app.Maomaoism
In Xcode 12.3, printing a log to Xcode's console seems still not working :(Byrne
A
19

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.

Absalom answered 13/5, 2020 at 19:34 Comment(0)
L
6

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
Levitus answered 31/10, 2019 at 12:37 Comment(0)
O
5

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
Ocd answered 29/4, 2022 at 3:42 Comment(0)
A
5

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")
        }
    }
}
Argue answered 13/8, 2022 at 21:27 Comment(0)
S
2

EDIT: Debug Preview is no longer supported in the latest versions of Xcode.

Very easy way to debug your Preview:

  1. Open your Swift project in Xcode 11.
  2. Right-click (or Control-click) on the Live Preview button in the bottom right corner of the preview.
  3. Select Debug Preview.

How to debug your SwiftUI previews in Xcode

Scorpius answered 5/9, 2020 at 16:59 Comment(2)
I am using XCode 13.4, I cannot get "Live Preview/Debug Preview" out. Am I doing something wrong or something has changed about this for the new XCode version?Cancellation
@JoeHuang I update the post. Thanks for the comment.Scorpius
I
2

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")
    }
  } 
}
Insight answered 4/3, 2021 at 19:7 Comment(0)
R
2

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

Redbreast answered 31/8, 2021 at 10:36 Comment(0)
A
1

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

Ash answered 9/6, 2019 at 22:11 Comment(0)
L
1

// Try this, add a 'return' on a view then the 'print' can stay alive in.

// Option 1

struct ContentView: View {
    var num: Int = 1
    
    var body: some View {
        print(num)
        
        return  Text("hello")
    
    }
}

// option 2

// **onto a button**

.onTapGesture {
  if true {
 print("Does the Universe have a Purpose?")
 }
Lozano answered 16/5, 2021 at 11:33 Comment(0)
B
1

This should work

if true {
      print(aVar, "xx")
}

return ZStack {
    ...
}
Berneicebernelle answered 16/2, 2022 at 3:59 Comment(0)
D
0

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.

Devest answered 19/7, 2019 at 10:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.