iOS 平台实现
选择先实现 iOS 再实现 Android 是随意的——说实话,你也可以先写 Android 实现,然后是 iOS,最后是 Web。或者三者任意组合。本教程只是恰巧选择了先实现 iOS。
你可能希望先实现 Web 层,因为它更接近插件的 API 定义。如果需要对 API 进行调整,在 Web 层工作时更容易发现这些问题。
向 Capacitor 注册插件
前提条件: 继续之前,请先熟悉 Capacitor 自定义原生 iOS 代码文档。
通过运行 npx cap open ios 在 Xcode 中打开 Capacitor 应用的 iOS 项目。右键单击 App 组(在 App 目标下),从上下文菜单中选择 New Group。将这个新组命名为 plugins。在 plugins 下再添加一个新组,命名为 ScreenOrientation。
完成后,你将得到路径 /App/App/plugins/ScreenOrientation/。右键单击 ScreenOrientation 组,从上下文菜单中选择 New File… 来添加以下文件:
ScreenOrientation.swift
ScreenOrientationPlugin.swift
ScreenOrientationPlugin.m
如果 Xcode 提示创建桥接头文件,请点击 Create Bridging Header。
将以下代码复制到 ScreenOrientationPlugin.m 中:
#import <Foundation/Foundation.h>
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(ScreenOrientationPlugin, "ScreenOrientation",
CAP_PLUGIN_METHOD(orientation, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(lock, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(unlock, CAPPluginReturnPromise);
)
这些 Objective-C 宏将插件注册到 Capacitor,使 ScreenOrientationPlugin 及其 方法在 JavaScript 中可用。
将以下代码复制到 ScreenOrientationPlugin.swift 中:
import Foundation
import Capacitor
@objc(ScreenOrientationPlugin)
public class ScreenOrientationPlugin: CAPPlugin {
@objc public func orientation(_ call: CAPPluginCall) {
call.resolve()
}
@objc public func lock(_ call: CAPPluginCall) {
call.resolve()
}
@objc public func unlock(_ call: CAPPluginCall) {
call.resolve();
}
}
注意使用了 @objc 装饰器;这些是确保 Capacitor 在运行时能够识别该类及其方法所必需的。
获取当前屏幕方向
首先解决获取当前屏幕方向的任务。打开 ScreenOrientation.swift 来设置类并编写获取当前方向的方法:
import Foundation
import UIKit
public class ScreenOrientation: NSObject {
public func getCurrentOrientationType() -> String {
let currentOrientation: UIDeviceOrientation = UIDevice.current.orientation
return fromDeviceOrientationToOrientationType(currentOrientation)
}
private func fromDeviceOrientationToOrientationType(_ orientation: UIDeviceOrientation) -> String {
switch orientation {
case .landscapeLeft:
return "landscape-primary"
case .landscapeRight:
return "landscape-secondary"
case .portraitUpsideDown:
return "portrait-secondary"
default:
// 默认情况:竖屏
return "portrait-primary"
}
}
}
接下来,在 ScreenOrientationPlugin.swift 中连接 orientation 方法,以调用实现类的方法:
@objc(ScreenOrientationPlugin)
public class ScreenOrientationPlugin: CAPPlugin {
private let implementation = ScreenOrientation()
@objc public func orientation(_ call: CAPPluginCall) {
let orientationType = implementation.getCurrentOrientationType();
call.resolve(["type": orientationType])
}
/* 其余代码为简洁起见省略 */
}
现在从 Xcode 运行应用,可以在真实设备或 iOS 模拟器上运行。加载完成后,你应该会在控制台中看到以下日志:
⚡️ To Native -> ScreenOrientation orientation 115962915
⚡️ TO JS {"type":"portrait-primary"}
注意: 日志的具体值会有所不同。在这个例子中,
115962915是从插件发起的方法调用分配的任意 ID。
你已经成功将原生 iOS 代码桥接到 Web 应用了!🎉
监听屏幕方向变化
当 UIDevice 触发 orientationDidChangeNotification 事件时,iOS 会通 过 NotificationCenter 通知我们用户旋转了设备。
load() 方法是注册此事件观察者的合适位置。同样,deinit() 方法是移除观察者的合适位置。
在观察者注册中,我们需要提供一个方法来将改变的方向返回给我们插件监听器,这些监听器正在监听我们作为插件 API 一部分定义的 screenOrientationChange 事件。我们可以重用 getCurrentOrientationType() 方法来获取改变后的屏幕方向。
将以下方法添加到 ScreenOrientationPlugin 类中:
override public func load() {
NotificationCenter.default.addObserver(
self,
selector: #selector(self.orientationDidChange),
name: UIDevice.orientationDidChangeNotification,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc private func orientationDidChange() {
// 如果方向未知、朝上或朝下,忽略方向变化
if(UIDevice.current.orientation.isValidInterfaceOrientation) {
let orientation = implementation.getCurrentOrientationType()
notifyListeners("screenOrientationChange", data: ["type": orientation])
}
}
iOS 会在三个维度检测方向变化。如代码注释所述,当方向变化不涉及横屏或竖屏方向时,我们将忽略通知监听器。## 锁定与解锁屏幕方向
iOS 并没有提供严格意义上的屏幕方向“锁定”或“解锁”机制。相反,它允许你通过编程方式设置允许的方向。
为此,我们需要在 AppDelegate.swift 文件的 AppDelegate 类中添加一个方法:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return ScreenOrientationPlugin.supportedOrientations
}
注意,这个函数返回的是 ScreenOrientationPlugin.supportedOrientations。这个属性目前还不存在,所以我们需要在 ScreenOrientationPlugin 类中将其添加为一个私有的静态类成员:
public static var supportedOrientations = UIInterfaceOrientationMask.all