15

I want to run this code on iOS 13 and above how should I fix this error? I want to make this code could run on iOS 13 too.

@available(iOS 14.0, *)
@main

struct WeatherProApp: App {
  @Environment(\.scenePhase) private var scenePhase
  @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  
  var body: some Scene {
    WindowGroup{
      let fetcher = WeatherFetcher()
      let viewModel = WeeklyWeatherViewModel(weatherFethcer: fetcher)
      WeeklyWeatherView(viewModel: viewModel)
    }
    .onChange(of: scenePhase) { (newScenePhase) in
      switch newScenePhase {
      case .active:
        print("scene is now active!")
      case .inactive:
        print("scene is now inactive!")
      case .background:
        print("scene is now in the background!")
      @unknown default:
        print("Apple must have added something new!")
      }
    }
  }
}

but it shows me this error

Error Image

Alireza12t
  • 377
  • 1
  • 4
  • 14
  • 1
    That error message is very clear. You cannot use the `@main` attribute on – Dávid Pásztor Jul 16 '20 at 12:53
  • Does this answer your question? [How to generate iOS 13 SwiftUI project in XCode?](https://stackoverflow.com/questions/69703928/how-to-generate-ios-13-swiftui-project-in-xcode) – lorem ipsum Mar 22 '22 at 11:56

6 Answers6

20

Following @the.blaggy answer, here is how I managed to run my project on iOS 13:

  1. Create a SceneDelegate if you do not have one

SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
  1. Open your info.plist as Source Code and add those lines :

Info.plist

   <key>UIApplicationSceneManifest</key>
       <dict>
           <key>UIApplicationSupportsMultipleScenes</key>
           <false/>
           <key>UISceneConfigurations</key>
           <dict>
           <key>UIWindowSceneSessionRoleApplication</key>
           <array>
               <dict>
                   <key>UISceneConfigurationName</key>
                   <string>Default Configuration</string>
                   <key>UISceneDelegateClassName</key>
                   <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
               </dict>
           </array>
       </dict>
   </dict>
  1. Add this in your WeatherProApp.swift

WeatherProApp.swift

    @main
    struct WeatherProAppWrapper {
        static func main() {
            if #available(iOS 14.0, *) {
                WeatherProApp.main()
            }
            else {
                UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(SceneDelegate.self))
            }
        }
    }
Ugo Marinelli
  • 989
  • 1
  • 11
  • 18
  • 3
    Thanks for the comment it was really helpful. Just adding a small comment because some people may struggle like me , if you don't find the `info.plist` because you created the app from Xcode 13, You can open the target then choose the info, and add any key, then the info.plist will appear in the project navigator, there you can edit it. – el3ankaboot Jan 17 '22 at 18:12
16

Actually you can use the @main attribute in pre iOS 14 but you need an alternative AppDelegate and SceneDelegate (you can copy these two delegate classes from iOS 13 Xcode projects) and you have to do some extra wrapping.

First you have to apply the @main attribute in the following way to a struct with a main function which decides depending on the iOS version whether to use the WeatherProApp struct or the AppDelegate class to launch:

@main
struct WeatherProAppWrapper {
    static func main() {
        if #available(iOS 14.0, *) {
            WeatherProApp.main()
        }
        else {
            UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
        }
    }
}

Afterwards you can use the shown implementation from your question, just remove the @main attribute, only use @available(iOS 14.0, *). E.g.:

@available(iOS 14.0, *)
struct WeatherProApp: App {
    var body: some Scene {
        WindowGroup{
            ContentView()
        }
    }
}

I'm not sure how familiar you're with UIKit but you have to do the same setup you did in your WindowGroup in the SceneDelegate class too.

the.blaggy
  • 815
  • 5
  • 16
  • Can you elaborate on what you would need to do in the SceneDelegate? If Xcode is reverting to `AppDelegate` in iOS 13 why would we need to use a. SceneDelegate? – GarySabo Nov 11 '20 at 18:50
  • 1
    I was able to get this working with an AppDelegate and Storyboard (and no SceneDelegate) – RanLearns Feb 09 '21 at 16:36
  • I can guarantee you for sure this "you can copy these two delegate classes from iOS 13 Xcode projects" is the worst advice one can give. – Farid Dec 07 '22 at 15:44
3

This might depend on other project code, but the following tested as works (Xcode 12b), so might be helpful.

The idea is to hide one wrapper inside another structure with availability checker:

@available(iOS 14.0, macOS 10.16, *)
struct Testing_SwiftUI2AppHolder {
    @main
    struct Testing_SwiftUI2App: App {

        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • What is this doing? If this workaround works so seamlessly why Swift developers saw the need to annotate it with higher level API level? – Farid Dec 07 '22 at 15:45
  • How will it work in iOS 13 as requested? – Gargo Jan 20 '23 at 14:29
2

If you really need it, just change @main to @UIApplicationMain, it should do the trick.

  • 3
    Xcode 13 complains that "@UIApplicationMain may only be used on 'class' declarations" – George Shaw Jan 04 '22 at 23:33
  • Looks like no one know what their workaround work but they think it works. Can you please elaborate on how and why this works? – Farid Dec 07 '22 at 15:54
1

Combining answers by @the.blaggy and @Ugo Marinelli, I made it work by modifying my AppDelegate, without having to create a new SceneDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(
        _: UIApplication,
        didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let window = UIWindow(frame: UIScreen.main.bounds)

        window.rootViewController = UIHostingController(
            rootView: ContentView()
        )
        self.window = window
        window.makeKeyAndVisible()
        return true
    }
}

I also wrapped my main struct like what @the.blaggy did.

happymacaron
  • 450
  • 5
  • 10
0

The answer from @the.blaggy is really helpful, thanks a lot for the main idea. I just had no AppDelegate and SceneDelegate to copy these two delegate classes from iOS 13 Xcode projects. I just need to support iOS13+ and macOS using same new SwiftUI views instead of creating old UIKit and AppKit ones.

AppDelegate allows us to use configurationForConnecting directly in Swift instead of using plist file.

And @UIApplicationDelegateAdaptor allows us to use same AppDelegate for iOS14+.

So, I've wrote MainAppWrapper, MainApp, AppDelegate, SceneDelegate in one file.

The file can be used as a start template for any SwiftUI project to run same SwiftUI's ContentView() under any OS: iOS13, iOS14+, macOS.

Just replace a whole @main struct onto this code and you will see same ContentView() under all OS.

import SwiftUI

@main
struct MainAppWrapper {
    static func main() {
        if #available(iOS 14.0, *) {
            MainApp.main()
        }
        else {
            #if os(iOS)
            UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil,
                NSStringFromClass(AppDelegate.self))
            #endif
        }
    }
}

@available(iOS 14.0, *)
struct MainApp: App {
    #if os(iOS)
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    #endif

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

#if os(iOS)
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
        config.delegateClass = SceneDelegate.self
        return config
    }
}

class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if #unavailable(iOS 14.0) {
          let contentView = ContentView()

          // Use a UIHostingController as window root view controller.
          if let windowScene = scene as? UIWindowScene {
              let window = UIWindow(windowScene: windowScene)
              window.rootViewController = UIHostingController(rootView: contentView)
              self.window = window
              window.makeKeyAndVisible()
          }
        }
    }
}
#endif
slamor
  • 3,355
  • 2
  • 14
  • 13