I wanted to log to the console in the middle of a SwiftUI chain but you can’t put “print” statements in SwiftUI code in most places (“Everything must be a View!” ;)). Since I was in a playground I couldn’t set a breakpoint that logged to the console (my standard strategy when in an application context). So I wrote this little thing:
extension View {
func printMessage(_ msg: Any...,
separator: String = " ",
terminator: String = "\n")
-> some View {
// Print them out as if not
// converted to an array.
for m in msg {
print(m,
separator,
terminator: "")
}
print()
return self
}
}
Use it like so:
import Cocoa
import PlaygroundSupport
import SwiftUI
extension View {
func printMessage(_ msg: Any...,
separator: String = " ",
terminator: String = "\n")
-> some View {
// Print them out as if not
// converted to an array.
for m in msg {
print(m,
separator,
terminator: "")
}
print()
return self
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello")
Text("World!")
.padding()
.background(
GeometryReader { proxy in
Color.clear
.printMessage("Info: ", proxy.size)
})
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PlaygroundPage.current.liveView = NSHostingView(rootView: ContentView())
Could well be a better way or issues with this, but it’s just something I put together quickly to solve a problem (“what is the value of proxy.size?”) and it seems to work, so thought I’d share it. Improvements or better ways to get the same capabilities are welcome 🙂
Xcode 11.3 & SwiftUI Previews: Dark Mode preview bug work-around, compact previews, multi-previews for ColorSchemes and Dynamic Text sizes.
Few small tips for previewing with SwiftUI that I just figured out.
Here’s the simple test ContentView.swift file to use in a standard single view iOS SwiftUI app template project in Xcode 11.3 that we’ll use to explore these tips:
So, the first thing is WOW that takes a lot of space for just a small horizontal cell I’m working on (like for list row or whatever). It’s especially annoying if you’re trying to make sure it works well in dark mode, at different dynamic type sizes, etc. First tip is how to make that work better.
1 – How to preview a horizontal or smaller view in less space in the Xcode Canvas
Context: you’re working on a custom horizontal control, or a row in a list and you want to do multiple previews for light & dark mode, or different text size classes.
By default each preview gets an entire iPhone worth of chrome around it which means you have to scroll to check all your variations out – tiresome! Avoid this by using the .previewLayout modifier with a size that’s sufficient for your view. For example:
struct Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
}
}
This produces a much more manageable preview like this:
The dimensions you use in .previewLayoutwill be specific to whatever you’re working on, of course, but this comes in really handy when you are previewing multiple versions of a horizontal view like this. Let’s demonstrate that with our next tip by making Dark Mode preview in Xcode 11.3 usable outside a NavigationView.
2 – Dark Mode Preview only seems to really work in NavigationViews
So the standard advice on how to preview in Dark Mode is to add .environment(.colorScheme, .dark) to the end of your view creation in PreviewProvider like so:
struct PreviewsDarkNotDark: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
.border(Color.green)
.environment(\.colorScheme, .dark)
}
}
To make it clear what’s going on I’ve added a green border modifier to our ContentView so that we can see where it is when previewed like this because otherwise we’d wonder if it even exists!
Kind of useless
Our views are using the colorScheme that was set and are rendering as if the device is in dark mode, but we can’t see it on a white background. So let’s fix that:
3 – Combining previews and reducing duplicate code
So now we have dark mode looking good so let’s combine the preceding two tips and look a both light and dark modes on the same screen and without having to scroll:
struct Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 400, height: 100))
}
}
struct PreviewsActuallyDarkMode: PreviewProvider {
static var previews: some View {
ZStack {
Color(.black)
ContentView()
}
.previewLayout(.fixed(width: 400, height: 100))
.environment(\.colorScheme, .dark)
}
}
Which looks like this:
Looking pretty good! While writing them in separate PreviewProviders like that helpfully uses their struct names to label them, it does involve a bit of duplicated code. Here’s an alternate way to do it with a ViewModifier to put shared modifiers in and a Group of views which Xcode makes each their own Preview:
This gives us four previews and adds the ability to name them since they’re all in the same PreviewProvider struct and so don’t get named individually any longer. Looks like this now:
Well, this “tiny tips” got a little long… but hopefully you found these small tips to be useful.
Just a simple utility to print a string in the middle of a view chain when doing SwiftUI (pardon narrow formatting for blog):
extension View {
func printMessage(_ msg: Any...,
separator: String = " ",
terminator: String = "\n")
-> some View {
// Print them out as if not
// converted to an array.
for m in msg {
print(m,
separator,
terminator: "")
}
print()
return self
}
}
Use like this:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
VStack {
TestView(label: "hello", idx: 22)
.font(.title)
TestView(label: "hello", idx: 24)
.foregroundColor(Color.red)
Spacer()
}
}
}
struct TestView: View {
let label: String
let idx: Int
var body: some View {
Text(label)
.padding(10)
.printMessage("\(self.label):", "\(self.idx)")
// or: .printMessage(self.label, self.idx)
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
// In a Swift playground the output is:
hello: 22
hello: 24
hello: 22
hello: 24
hello: 22
hello: 24
Assuming you have a ContentView defined and want to see it in the Swift playground preview with a landscape aspect ratio, this code snippet seems to work:
let view = UIHostingController(rootView: ContentView())// pick whatever size you want (this is iPhone 8 div 2):
view.preferredContentSize = CGSize(width: 667, height: 375)
PlaygroundPage.current.liveView = view
Might be a better way, but when I didn’t find a menu item in Xcode 11.3 I just coded this in to move forward. Seems to work to check layout, though it’s not strictly “landscape orientation” from a device standpoint ¯\_(ツ)_/¯: