iOS开发Xcode配置

Xcode 13 SwiftUI 工程的一些Tips

1.AppDelegate和 SceneDelegate

Xcode升级到13以后,Interface只有SwiftUI 和 Storyboard两个选项了。

建好以后的工程,可以看到AppDelegate和 SceneDelegate 和 info.plist, 我熟悉的AppDelegate去哪里了?

多出来一个DemoxxxApp,这个是什么鬼?,这哥们叫 DemoxxxApp(项目名称), 是SwiftUI部门的,平时的行为规范是App, body是他们的工装,出门就得穿,这工装也是有规范的,这规范叫Scene。 我原来熟悉的UIKit,AppKit,已经退居二线了,Apple正计划让他们退休呢。这App啊,顶的就是UIApplicationDelegate的班。

DemoxxxApp告诉我,他们也可以按照UIApplicationDelegate的方式来工作,只不过需要我额外做一点工作。我看了下,那大概就是需要声明一个符合UIApplicationDelegate协议的类,然后使用@UIApplicationDelegateAdaptor的注解标记该类即可。

import SwiftUI

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        print("log-didFinishLaunching")
        return true
    }
    func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
        print("log-DidReceiveMemoryWarning")
    }
}

@main
struct DemoxxxApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

SceneDelegate怎么办?再经过一番攀谈交心,他告诉我,之前Window那一套,已经不管用了,现在得用Scene。说着,甩出了一份文档

When your app’s state changes, UIKit notifies you by calling methods of the appropriate delegate object:
In iOS 13 and later, use UISceneDelegate objects to respond to life-cycle events in a scene-based app.
In iOS 12 and earlier, use the UIApplicationDelegate object to respond to life-cycle events.

Note
If you enable scene support in your app, iOS always uses your scene delegates in iOS 13 and later. In iOS 12 and earlier, the system uses your app delegate.

哦,这文档说的是,iOS13以前,由UIApplicationDelegate来控制声明周期,iOS13以后,由UISceneDelegate来控制声明周期。在iOS 13之后,用UIScene替代了之前UIWindow来管理视图。主要是为了解决iPadOS展示多窗口的问题。

在iOS 14之后,Apple又给SwiftUI提供了更优雅的API来显示和控制Scene。所以控制应用展示可以这样:

@main
struct DemoxxxApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @Environment(\.scenePhase) var scenePhase
    var body: some Scene {
        WindowGroup {
            ContentView()
        }.onChange(of: scenePhase) { newScenePhase in
            switch newScenePhase {
            case .active:
              print("应用启动了")
            case .inactive:
              print("应用休眠了")
            case .background:
              print("应用在后台展示")
            @unknown default:
              print("default")
            }
        }
    }
}

另外:openURL,userActivity等可以直接挂在某一个具体的View上,来进行单独处理,不需要再通过AppDelegate中转了。

这下就搞定了,又可以开始我愉快的旅程了。

2.info.plist

这个文件已经默认不生成了,可以在info下面直接编辑。

Projects created from several templates no longer require configuration files such as entitlements and Info.plist files. Configure common fields in the target’s Info tab, and build settings in the project editor. These files are added to the project when additional fields are used. (68254857)

3.WatchOS8以后,工程里面同样没有了ExtensionDelegate。
import SwiftUI

@main
struct xxxxxApp: App {
    
    @WKExtensionDelegateAdaptor(ExtensionDelegate.self) var extensionDelegate
    
    @Environment(\.scenePhase) var scenePhase
    @SceneBuilder var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
            }
        }.onChange(of: scenePhase) { newScenePhase in
            switch newScenePhase {
            case .active:
                print("active")
            case .inactive:
                print("inactive")
            case .background:
                print("background")
            @unknown default:
                print("default")
            }
        }

        WKNotificationScene(controller: NotificationController.self, category: "myCategory")
    }
}



import WatchKit

class ExtensionDelegate: NSObject, WKExtensionDelegate, CLLocationManagerDelegate {
    var locationManager: CLLocationManager?

    func applicationDidFinishLaunching() {
       
    }

    func applicationDidBecomeActive() {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillResignActive() {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, etc.
    }

    func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
        // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
        for task in backgroundTasks {
            // Use a switch statement to check the task type
            switch task {
            case let backgroundTask as WKApplicationRefreshBackgroundTask:
                // Be sure to complete the background task once you’re done.
                backgroundTask.setTaskCompletedWithSnapshot(false)
            case let snapshotTask as WKSnapshotRefreshBackgroundTask:
                // Snapshot tasks have a unique completion call, make sure to set your expiration date
                snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
            case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
                // Be sure to complete the connectivity task once you’re done.
                connectivityTask.setTaskCompletedWithSnapshot(false)
            case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
                // Be sure to complete the URL session task once you’re done.
                urlSessionTask.setTaskCompletedWithSnapshot(false)
            case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
                // Be sure to complete the relevant-shortcut task once you're done.
                relevantShortcutTask.setTaskCompletedWithSnapshot(false)
            case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
                // Be sure to complete the intent-did-run task once you're done.
                intentDidRunTask.setTaskCompletedWithSnapshot(false)
            default:
                // make sure to complete unhandled task types
                task.setTaskCompletedWithSnapshot(false)
            }
        }
    }
}
4.总结
  • 在iOS13后,Scene取代了Window,来做视图的管理和呈现。
  • 在SwiftUI中,可以控制场景的切入,切出(取代applicationDidBecomeActive,applicationWillResignActive, applicationDidEnterBackground),openURL,userActivity等事件。
  • SwiftUI现在还无法处理applicationDidReceiveMemoryWarning,applicationWillTerminate等回调,需要通过@UIApplicationDelegateAdaptor的方式来实现。