SwiftUI Problem with main attribute only in Previews - swiftui

I am running a SwiftUI app (Xcode 12.3) using the App approach:
#main
struct ThingsApp: App {
...
}
This works as expected both on simulators and my device. But it does not work in previews: There, I get the error of 'main' attribute cannot be used in a module that contains top-level code.
There must be something about my app that's causing it, since if I set up a project from scratch in Xcode, this approach works. But I don't quite know how to figure out what exactly would cause this. Fuller trace below:
'main' attribute cannot be used in a module that contains top-level code
----------------------------------------
CompileDylibError: Failed to build ThingsApp.swift
Compiling failed: 'main' attribute cannot be used in
a module that contains top-level code
/Users/cg/Library/Developer/Xcode/DerivedData/
Things-bkpepcogttixysdvumdszlfwxfix/Build/
Intermediates.noindex/
Previews/Things/Intermediates.noindex/Things.build/
Debug-iphonesimulator/Things.build/Objects-normal/x86_64/
ThingsApp.2.preview-thunk.swift:8:1: error: 'main'
attribute cannot be used in a module that contains top-level code
#main extension ThingsApp {
^
/Users/cg/Library/Developer/Xcode/DerivedData/
Things-bkpepcogttixysdvumdszlfwxfix/Build/
Intermediates.noindex/
Previews/Things/Intermediates.noindex/
Things.build/
Debug-iphonesimulator/Things.build/
Objects-normal/x86_64/
ThingsApp.2.preview-thunk.swift:1:1: note: top-level code defined in this source file
#_private(sourceFile: "ThingsApp.swift") import Things

For me it was a top level class type I created in the App extension:
#main
struct MyApp: App {
var body: some Scene {}
class AppDelegate: NSObject, UIApplicationDelegate {...}
}
Once I move it outside and did a clean build, previews worked again:
#main
struct MyApp: App {
var body: some Scene {...}
}
private class AppDelegate: NSObject, UIApplicationDelegate {...}

if you are using environment your code should look like this
#main
struct YourApp: App {
#StateObject private var someData = SomeData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(someData)
}
}
}

Related

Detecting hardware keyboard key press under iPadOS 14.x with SwiftUI 2.0

i'm trying to add hardware keyboard support for an iOS 14.0 app written in SwiftUI 2.0.
I saw some examples working with UIHostingController, so i'd like to try this way on iOS14/SWiftui 2.0 using WindowGroup.
I'm gettin gerror when compiling in XCODe 12.3
"Generic struct 'WindowGroup' requires that 'KeyTestController' conform to 'View'"
ContentView() conforms to View and everything is working fine when not using the "KeyTestController" class.
Any way to solve this ?
Thank you very much.
import SwiftUI
import StoreKit
import UIKit
#main
struct myApp: App
{
var body: some Scene
{
WindowGroup
{
KeyTestController(rootView:ContentView())
}
}
}
class KeyTestController<Content>: UIHostingController<Content> where Content: View
{
/* CODE …… */
}

In a SwiftUI AppLifecycle Document App, how can I get a menu command in active ContentView?

I'm writing a MacOS document app using the SwiftUI App lifecycle, and all the tricks I see here and elsewhere for sending a menu action to the active window depend on using platform specific implementation, which is (mostly) unavailable in a SwiftUI Lifecycle app. What I'm looking for is something like SideBarCommands(), which adds a menu item that, when selected by mouse or command key, toggles the appearance of the sidebar in the active window. All the Command examples I have seen thus far are trivial, none address a multi-document, multi-window use case.
Given a ContentView declared thusly:
struct ContentView: View {
#Binding var document: TickleDocument
var body: some View {
TextEditor(text: $document.text)
}
public func menuTickle() {
document.text = "Wahoo!"
}
}
and a command, which is added via:
struct TickleApp: App {
public static var target:TickleDocument?
var body: some Scene {
let docGroup = DocumentGroup(newDocument: TickleDocument()) { file in
ContentView(document: file.$document)
}
docGroup
.commands {
CommandMenu("App Tickles") {
Button("Tickle The ContentView") {
// Here's where I need to call menuTickle() on the active ContentView
}.keyboardShortcut("t")
}
}
}
}
}
What do I need to do so the button closure can call menuTickle() on the active ContentView? I know it can be done, because SideBarCommands() does it (unless Apple is using some non-public API to do it...).
For bonus points, tell me how I can detect whether or not I'm the active ContentView while body is being evaluated, and how I can detect when it changes! Tracking the Environment variable scenePhase is worthless - it always reports active, and never changes.
My question is a duplicate of this one.
The answer to that question contains a link to a solution that I have verified works, and can be found here

How to accept CloudKit shares with the new SwiftUI app lifecycle?

In the iOS 13 world, I had code like this:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
// do stuff with the metadata, eventually call CKAcceptSharesOperation
}
}
I am migrating my app to the new SwiftUI app lifecycle, and can’t figure out where to put this method. It used to live in AppDelegate pre-iOS13, and I tried going back to that, but the AppDelegate version never gets called.
There doesn’t seem to be a SceneDelegateAdaptor akin to UIApplicationDelegateAdaptor available, which would provide a bridge to the old code.
So, I’m lost. How do I accept CloudKit shares with SwiftUI app lifecycle? 🙈
You can still use AppDelegate with SwiftUI's new life-cycle until Apple releases APIs to handle this natively in SwiftUI’s App Life-cycle.
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
}
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Read this for more

How can I "forward" the entire SwiftUI environment to another view?

It seems that certain SwiftUI views create new environment contexts, such as NavigationLink. None of the environment is available in the new view.
As a workaround, I've been just manually forwarding through environment variables, like this:
struct ExampleView: View {
#EnvironmentObject var foo: UserStore
#EnvironmentObject var bar: UserStore
var body: some View {
NavigationLink(destination:
SomeOtherView()
.environmentObject(self.foo)
.environmentObject(self.bar)
) {
Text("Open View")
}
}
}
However this seems broken, as it violates the purpose of the environment. Also, it's confusing because it's not clear (or documented?) where these boundaries are and it causes a runtime error when a view depends on a missing EnvironmentObject.
Is there a better way to do this?
Similarly, I want to create a wrapper UIViewControllerRepresentable that can contain SwiftUI children (via UIHostingController) and I would like those children to have access to the environment as well.

'ContentView_Previews' is not a member type of error

'ContentView_Previews' does not compile if ContentView references an external object.
If I remove all references to #ObservedObject, preview compiles.
import SwiftUI
struct ContentView: View {
#ObservedObject var fancyTimer = FancyTimer()
var body: some View {
Text("\(fancyTimer.timerValue)")
.font(.largeTitle)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import Foundation
import SwiftUI
import Combine
class FancyTimer: ObservableObject {
#Published var timerValue: Int = 0
init() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true)
{ timer in
self.timerValue += 1
}
}
}
Error is: 'ContentView' is not a member type of 'FancyTimer'
Often the problem is that you created a class, a struct, or enum that has the same name as the module you are in.
Here, odds are that "FancyTimer" is also the name of your project, which triggers the error.
Try to change the class name.
Changing display name and bundle identifier in general settings fixed this issue for me in Xcode 11.1.
I just ran into this, and clicking the "Diagnostics" button on the Preview panel showed me the problem. Is your project called "FancyTimer"? If so, what is happening is that the compiler is trying to reference FancyTimer.ContentView_Previews, and is failing because it's not a sub-type of that class. Basically you're getting a conflict between project name and class name.
My projectname was the same as one of the Foundation's classes. Changing the Projectname fixed the issue.
Issue seems to be gone in Xcode 11 GM Seed 2