1

I'm trying to use the new .backgroundTask modifier announced at WWDC '22 but I can't get the below to compile? Xcode says Cannot infer contextual base in reference to member 'appRefresh' How can I do this?

import SwiftUI
import Foundation
import BackgroundTasks

  var body: some Scene {
      WindowGroup {
          if #available(iOS 16, *) {
              AppRootView()
                  .backgroundTask(.appRefresh("refreshAllData")) {
                      refreshData() 
                  }
          } else {
              AppRootView()
          }
      }
  }

Edit: as agentBilly said .backgroundTask is a scene modifier, so should look like this but I'm still unsure how to check for iOS 16 availability?

import SwiftUI
import Foundation
import BackgroundTasks


@main
struct myApp: App {
    
    @Environment(\.scenePhase) var scenePhase
   
    var body: some Scene {
        WindowGroup {
            AppRootView()
        }
        .backgroundTask(.appRefresh("refreshAllData")) {
            refreshAllData() 
        }
}
GarySabo
  • 5,806
  • 5
  • 49
  • 124
  • You need to put everything new into separated view and use compiler-time `@available` modifier and `#if canImport`. Here is an example https://stackoverflow.com/a/72933630/12299030. – Asperi Jul 18 '22 at 19:36
  • The new `.backgroundTask(_:action:)` seems to be a scene modifier, could you provide more details on `AppRootView`? – AgentBilly Jul 18 '22 at 19:38
  • @AgentBilly you are correct, see my edited Question. – GarySabo Jul 18 '22 at 20:02
  • @Asperi see my edit, how to check availability like in that question but for a `Scene`? – GarySabo Jul 18 '22 at 20:03
  • @GarySabo check out [this](https://www.avanderlee.com/swiftui/conditional-view-modifier/), you can put the availability check in a custom conditional modifier. btw, did it solve your original issue with the implicit member chaining? – AgentBilly Jul 18 '22 at 20:06
  • @AgentBilly thanks I actually have used this before for a `View` but this doesn't work for a `Scene` and simply changing out `Scene` for `View` in the custom conditional modifier doesn't work. – GarySabo Jul 18 '22 at 20:19
  • Yeah sorry haha I just realized that it only works with Views since scenes don't have a `@SceneModifier`, I'm afraid you'd have to go for the dirty solution, which is introducing an if-else branch in your `body` (don't know if that works, maybe you can try it out). – AgentBilly Jul 18 '22 at 20:21
  • @Asperi your solution worked in a test app but implementing it in my actual production app failed for `error build: Command SwiftCompile failed with a nonzero exit code` and that's all it gives me so I'm not really sure why. – GarySabo Jul 19 '22 at 00:20

1 Answers1

2

We cannot use condition right in scene body, because SceneBuilder does not support conditional expression, so a solution is to have completely different apps loaded depending on availability.

Simplified code to have less lines:

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

@available(iOS 16, *)
struct RefreshingAppScene: Scene {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .backgroundTask(.appRefresh("refreshAllData")) {
            refreshAllData()
        }
    }
}

@main
struct AppLoader {
    static func main() {
        if #available(iOS 16, *) {  // << conditional availability !!
            New_iOSApp.main()
        } else {
            Old_iOSApp.main()
        }
    }
}

@available (iOS 16, *)
struct New_iOSApp: App {
    var body: some Scene {
        RefreshingAppScene()
    }
}

struct Old_iOSApp: App {
    var body: some Scene {
        AppScene()
    }
}

Tested with Xcode 14b3 / iOS 16

Asperi
  • 228,894
  • 20
  • 464
  • 690