Xcode swift preview fails with 'Unknown preview provider "<classname>_"' - xcode

There are a few similar questions here, but no answers resolve the issue for me.
My project doesn't start with a number.
It is essentially just this:
import Foundation
import UIKit
class DetailsView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupBackgroud()
setupViews()
}
func setupBackgroud() {}
func setupViews() {}
}
#if DEBUG
import SwiftUI
#available(iOS 13, *)
struct DetailsViewPreview: PreviewProvider {
static var previews: some View {
// view controller using programmatic UI
DetailsView().showPreview()
}
}
#endif
and I get the error:
I have no idea what the 'mangled name' is.

The stupidest fix possible for this error.
I removed the compiler pre-proccessor statements.
Just remove #if DEBUG and #endif.
"It Just Works" 🤦‍♂️

Related

Static method 'buildBlock' requires that 'ToolbarItem<Void, Text>' conform to 'View'

I'm supporting iOS 13 and up. I wish to use the new .toolbar modifier for an optional feature, in devices using iOS 14 and up. I'm using Xcode 13.0 RC 1, build 13A233, but the issue also occurs in Xcode 12.5.1. I have created the following code:
import SwiftUI
struct MyToolbar<T>: ViewModifier where T: View {
let toolbarContent: () -> T
func body(content: Content) -> some View {
if #available(iOS 14.0, *) {
content
.toolbar(content: self.toolbarContent)
}
}
}
extension View {
func myToolbar<T: View>(#ViewBuilder content: #escaping () -> T) -> some View {
self.modifier(MyToolbar(toolbarContent: content))
}
}
struct MyToolbar_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
Text("Hello, world")
.myToolbar {
if #available(iOS 14.0, *) {
Text("Hello, world!")
// ToolbarItem(placement: .bottomBar) {
// Text("Hello, world!")
// }
} else {
EmptyView()
}
}
}
}
}
I have included the preview bit, so the above can be pasted into an empty file in any Xcode SwiftUI project. It works. When I uncomment the ToolbarItem statement, I get the following errors:
Static method 'buildBlock' requires that 'ToolbarItem<Void, Text>' conform to 'View'
Static method 'buildLimitedAvailability' requires that 'ToolbarItem<Void, Text>' conform to 'View'
How can I fix these errors?
I think the main issue is that the .toolbar modifier's block is defined to return something conforming to the ToolbarContent protocol. From the docs:
func toolbar<Content>(content: () -> Content) -> some View where Content : ToolbarContent
As such, within your view modifier
func body(content: Content) -> some View {
if #available(iOS 14.0, *) {
content
.toolbar(content: self.toolbarContent)
}
}
… you need to ensure that self.toolbarContent matches that () -> ToolbarContent type signature.
Just from glancing at your code, I'd be inclined to first try changing your conformance of T from T: View to T: ToolbarContent. By eye that looks like it might be enough, but it's possible you might need to do a bit more work to tidy everything up.

macOS & SwiftUI 2: simplest way to turn off beep on keystroke

The following trivial macOS app is written in SwiftUI 2.0.
import SwiftUI
#main
struct TempApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
struct ContentView: View {
var body: some View {
Text("Hello, beep!").padding()
}
}
When in the foreground, this app will emit an error beep on certain keystrokes (like "a"). What's the simplest way to suppress this beep?
An Xcode project illustrating this (and the answer) can be found here.
There are many older related questions on SO, but none of these are specifically about doing this in SwiftUI 2.0.
You can suppress the beep by adding a local monitor for the .keyDown event at the top level. This can be done simply in ContentView.init(), like so:
struct ContentView: View {
var body: some View {
Text("Hello, silence!").padding()
}
init() {
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { _ in return nil }
}
}
This technique was inspired by this answer.

Streamlining SwiftUI Previews in Xcode

There's a lot of code my app normally runs that I would like to skip in Previews. Code that is time-consuming and has no visible effects (such as initializing audio devices). I'm trying to figure out how skip it for previews.
There is an easy way to run code only in the production build of an app using the DEBUG macro. But I don't know of anything similar for non-Preview builds (because Previews presumably build the same code as non-Previews).
I thought that setting a variable, previewMode, within my ViewModel, would work. That way I could set it to true only within the PreviewProvider:
struct MainView_Previews: PreviewProvider {
static var previews: some View {
let vm = ViewModel(previewMode: true)
return MainView(viewModel: vm)
}
}
and when I created the ViewModel within the SceneDelegate, I could set previewMode to false:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let vm = ViewModel(previewMode: false)
let mainView = MainView(viewModel: vm)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: mainView)
self.window = window
window.makeKeyAndVisible()
}
}
so that I can enclose any code I don't want to run for previews in if !previewMode { ••• }
Unfortunately the code is still running. Evidently the scene() function is getting called whenever my preview updates. :(
How can I specify code to not run for previews?
thanks!
The only working solution I've found is to use the ProcessInfo.processInfo.environment value for key XCODE_RUNNING_FOR_PREVIEWS. It's set to "1" only when running in preview mode:
let previewMode: Bool = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
See this post.
Practically Live-Preview mode run-time does not differ much from Simulator Debug mode run-time. And this, of course, as intended to give us quick (as possible) feedback of our code execution.
Anyway here are some findings... that might be used as solution/workaround for some cases that detection of Preview is highly desirable.
So created from scratch SwiftUI Xcode template project and in all functions of generated entities add print(#function) instruction.
ContentView.swift
import SwiftUI
struct ContentView: View {
init() {
print(#function)
}
var body: some View {
print(#function)
return someView()
.onAppear {
print(#function)
}
}
private func someView() -> some View {
print(#function)
return Text("Hello, World!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
print(#function)
return ContentView()
}
}
Perform Debug Preview and see output:
application(_:didFinishLaunchingWithOptions:)
application(_:configurationForConnecting:options:)
scene(_:willConnectTo:options:)
init()
sceneWillEnterForeground(_:)
sceneDidBecomeActive(_:)
2020-06-12 16:08:14.460096+0300 TestPreview[70945:1547508] [Agent] Received remote injection
2020-06-12 16:08:14.460513+0300 TestPreview[70945:1547508] [Agent] Create remote injection Mach transport: 6000026c1500
2020-06-12 16:08:14.460945+0300 TestPreview[70945:1547482] [Agent] No global connection handler, using shared user agent
2020-06-12 16:08:14.461216+0300 TestPreview[70945:1547482] [Agent] Received connection, creating agent
2020-06-12 16:08:15.355019+0300 TestPreview[70945:1547482] [Agent] Received message: < DTXMessage 0x6000029c94a0 : i2.0e c0 object:(__NSDictionaryI*) {
"updates" : <NSArray 0x7fff8062cc40 | 0 objects>
"id" : [0]
"scaleFactorHint" : [3]
"providerName" : "11TestPreview20ContentView_PreviewsV"
"products" : <NSArray 0x600000fcc650 | 1 objects>
} > {
"serviceCommand" : "forwardMessage"
"type" : "display"
}
__preview__previews
init()
__preview__body
__preview__someView()
__preview__body
__preview__body
__preview__someView()
__preview__body
As it is clear complete workflow of app launching has been performed at start AppDelegate > SceneDelegate > ContentView > Window and only after this the PreviewProvider part.
And in this latter part we see something interesting - all functions of ContentView in Preview mode have __preview prefix (except init)!!
So, finally, here is possible workaround (DISCLAIMER!!! - on your own risk - only demo)
The following variant
struct ContentView: View {
var body: some View {
return someView()
.onAppear {
if #function.hasPrefix("__preview") {
print("Hello Preview!")
} else {
print("Hello World!")
}
}
}
private func someView() -> some View {
if #function.hasPrefix("__preview") {
return Text("Hello Preview!")
} else {
return Text("Hello World!")
}
}
}
Gives this

Passing an environmentObject from an NSHostingController to a SwiftUI view

I have a macOS storyboard app in which I am having a SwiftUI view which hosted using an NSHostingController. I need to pass an EnvironmentObject to this SwiftUI view from my NSHostingController. I am unable to achieve this. I have currently written this code but it does not work. What could be the issue here?
import Cocoa
import SwiftUI
class SearchText: ObservableObject {
#Published var text = ""
}
class HostingController: NSHostingController<SwiftUIView> {
#objc required dynamic init?(coder: NSCoder) {
super.init(coder: coder, rootView: SwiftUIView().environmentObject(SearchText()))
}
}
The issue is that modifier .environmentObject returns different type than you specify in generics, ie SwiftUIView.
Here is possible approach
class HostingController: NSHostingController<AnyView> {
#objc required dynamic init?(coder: NSCoder) {
super.init(coder: coder, rootView:
AnyView(SwiftUIView().environmentObject(SearchText())))
}
}
Alternate: (Xcode 13.4)
class HostingController: NSHostingController<HostingController.HelperView> {
struct HelperView: View {
var body: some View {
SwiftUIView().environmentObject(SearchText())
}
}
#objc required dynamic init?(coder: NSCoder) {
super.init(coder: coder, rootView: HelperView())
}
}

SwiftUI lists in Xcode Playground

I'm trying to learn SwiftUI in an Xcode playground. I was trying to make a simple list (should be easy right?) but Xcode crashes the playground while it does work in an iOS project.
This is my code:
import UIKit
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
var body: some View {
List(0..<5) { item in
Text("Test")
}
}
}
let viewController = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = viewController
Can't fix the issue but anyway you definitely should checkout swift playgrounds, they are great https://www.apple.com/swift/playgrounds/ :)
This might be a bug in Xcode 12.5, but everything works well in Xcode 13.0 Beta
Also, In SwfitUI, you can use PlaygroundPage.current.setLiveView instead of creating a hosting controller.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
List(1..<5) {
Text("Item \($0)")
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
This is what it looks like:

Resources