story-content-ios
安装
CocoaPods
CocoaPods是Cocoa项目的依赖性管理器。要使用CocoaPods将StoryContent集成到项目中,请在您的Podfile
中指定它。
pod 'StoryContent', '~> 0.4.0'
手动
如果您更喜欢不使用CocoaPods依赖性管理器,您可以手动将StoryContent.framework添加到您的项目中。
StoryContent
StoryContent包含以下模块:
- SCLMAuthService - 身份验证
- SCLMSyncService - 访问StoryCLM的RESTful API
- SCLMSyncManager - 同步管理器
- SCLMBridge - 桥接器
设置
要处理StoryContent,需要安装SCLMAuthService和SCLMSyncService,从AuthCredentials.plist中获取数据。
SCLMAuthService.shared.setClientId(clientId)
SCLMAuthService.shared.setClientSecret(clientSecret)
SCLMAuthService.shared.setAppId(appId)
SCLMAuthService.shared.setAppSecret(appSecret)
SCLMAuthService.shared.setAuthEndpoint(authEndpoint)
登录
要以用户身份进行授权,需要调用以下方法
SCLMAuthService.shared.login(username: username, password: password, success: {
success()
}) { (error) in
failure(error)
}
成功认证后,将获得token,该token随后将被SCLMSyncService用于访问RESTful API
要以应用身份进行授权,需要调用以下方法
func authAsApplication(clientId: String,
secret: String,
username: String,
password: String,
success: @escaping (SCLMToken?) -> Void,
failure: @escaping (Error) -> Void) {}
要以客户端身份进行授权,需要调用以下方法
func authAsService(clientId: String,
secret: String,
success: @escaping (SCLMToken?) -> Void,
failure: @escaping (Error) -> Void) {}
同步客户端
登录成功后,需要执行客户端同步
SCLMSyncManager.shared.synchronizeClients { (error) in
}
此方法将下载所有可用的客户端及为每个客户端的演示
访问数据
可通过NSFetchedResultsController访问数据
可以使用具有sectionNameKeyPath: "client.name"的现有NSFetchedResultsController
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> {
return SCLMSyncManager.shared.fetchedResultsController
}
或者可以这样初始化
public lazy var fetchedResultsController: NSFetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in
let fetchResult = self.fetchRequest(for: Presentation.entityName(), batchSize: 100, sortKey: "client.name", context: syncManager.context)
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchResult, managedObjectContext: syncManager.context, sectionNameKeyPath: "client.name", cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
print("FetchedResultsController performFetch error")
}
return fetchedResultsController
}()
或者这样
public lazy var fetchedResultsControllerSectionLess: NSFetchedResultsController = { () -> NSFetchedResultsController<NSFetchRequestResult> in
let fetchResult = self.fetchRequest(for: Presentation.entityName(), batchSize: 100, sortKey: "client.name", context: syncManager.context)
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchResult, managedObjectContext: syncManager.context, sectionNameKeyPath: nil, cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
print("FetchedResultsController performFetch error")
}
return fetchedResultsController
}()
默认情况下,客户端(Client)的数量对应于UITableViewDataSource或UICollectionViewDataSource的分区数量
func numberOfSections(in collectionView: UICollectionView) -> Int {
if let sections = viewModel.fetchedResultsController.sections {
return sections.count
}
return 0
}
分区对象(Presentation)的数量对应于客户端的演示
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let sections = viewModel.fetchedResultsController.sections {
if sections.count > section {
return sections[section].numberOfObjects
}
}
return 0
}
通过以下方式访问演示
let presentation = fetchedResultsController.object(at: indexPath) as! Presentation
同步演示
当同步客户端时,仅同步演示模型。要同步演示内容,需要调用
SCLMSyncManager.shared.synchronizePresentation(presentation, completionHandler: { (error) in
completionHandler(error)
}) { (progress) in
progressHandler(progress)
}
在更新数据时恢复同步进度,需要使用以下工具
let presentationSynchronizingNow = SCLMSyncManager.shared.isPresentationSynchronizingNow(presentation: presentation)
如果内容正在下载,则SCLMSyncManager.shared.isPresentationSynchronizingNow方法将返回具有以下属性的对象
public weak var downloadRequest: DownloadRequest?
public var progress = Progress() {
didSet {
progressHandler?(presentationId.intValue, progress)
}
}
public var progressHandler: ((_ presentationId: Int?, _ progress: Progress) -> Void)?
public var completionHandler: ((_ presentationId: Int?) -> Void)?
因此,对于 downloadRequest 可能会调用 cancel() 来取消,progress 传递当前进度,progressHandler 和 completionHandler 可以用于控制和更新界面
要删除内容,需要调用
SyncManager.shared.deletePresentationContentPackage(presentation)
要更新演示,需要调用
SCLMSyncManager.shared.updatePresentation(presentation) { (error) in
completionHandler(error)
}
分析
分析在 StoryIot 库中实现,通过 CocoaPods 连接
pod 'StoryIoT', :git => 'https://github.com/storyclm/story-iot-ios.git', :tag => ‘develop’
SLSessionsSyncManager 通过 AppDelegate 初始化
SLSessionsSyncManager.shared.startTimer()
并监听存储库桥接中添加的所有事件,并在检测到时将它们发送到服务器。
初始化时创建
storyIoT = StoryIoT(credentials: SC)
它直接负责发送消息
要成功初始化 StoryIoT,需要访问权限
endpoint=
hub=
key=
secret=
expiration=
桥
StoryContent 允许开发者使用仅限 HTML、CSS 和 JavaScript 等网页技术以及 StoryBridge 技术来创建具有类似功能和可靠性的工业级内容(演示)
StoryBridge 是 Breffi 开发的技术,允许以高可靠性和异步方式从内容调用客户端应用程序的本地代码
基本上,StoryBridge 由两部分组成
- SCLMBridgeModule 模块,它在本地代码端实现,是客户端应用程序的一部分;
- storyclm.js - 嵌入到内容中的库。
storyclm.js 是提供一个到 Story 平台系统函数(API)访问的库。此库应在 Story 中的 HTML5 应用程序中使用。在其他 CLM 系统中以及没有 Story 的情况下,此库不会工作。
主要任务是通过发送消息到 StoryBridge 并处理传入的消息。这是 StoryBridge 的内容端的一部分。Web 应用程序调用库的方法,该库再创建一个命令并发送到 StoryBridge 的本地部分,执行后,客户端应用程序使用桥接发送结果(命令)到 WebView,然后在 WebView 中捕获这个命令和数据。这样,本地代码的结果就会返回到 Web 应用程序。Web 应用程序对 WebView 属于哪个操作系统无关紧要,它只是操作库的方法。这样,应用程序可以在所有 StoryContent 客户端上以相同的方式工作,而不管操作系统是什么。库负责 Web 应用程序端的交互,并且是它的一部分。库有适用于所有操作系统的单一实现。
SCLMBridgeModule 是 StoryBridge 的本地代码端的一部分,它能够接收 WebView 和内容的消息,找到并启动处理模块,并将结果工作返回到 WebView。此模块管理消息的分发过程,并负责可靠的消息处理。
现有模块及其协议
SCLMBridgeBaseModule
public protocol SCLMBridgeBaseModuleProtocol: class {
func goToSlide(_ slide: Slide, with data: Any)
func getNavigationData() -> Any
}
SCLMBridgePresentationModule
public protocol SCLMBridgePresentationModuleProtocol: class {
typealias SlideName = String
func openPresentation(_ presentation: Presentation, with slideName: String?, and data: Any?)
func getPreviousSlide() -> Slide?
func getNextSlide() -> Slide?
func getCurrentSlideName() -> String?
func getBackForwardList() -> [SlideName]?
func getBackForwardPresList() -> [Presentation]?
func closePresentation(mode: ClosePresentationMode)
}
SCLMBridgeUIModule
public protocol SCLMBridgeUIModuleProtocol: class {
func hideCloseBtn()
func hideSystemBtns()
}
SCLMBridgeHTTPModule
// обрабатывает комманды
let commands = [command.httpget,
command.httppost,
command.httpput,
command.httpdelete]
SCLMBridgeSessionsModule
public protocol SCLMBridgeSessionsModuleProtocol: class {
func setSessionComplete()
}
SCLMBridgeCustomEventsModule
public protocol SCLMBridgeCustomEventsModuleProtocol: class {
func setEventKey(_ key: String, and value: Any)
}
SCLMBridgeMediaFilesModule
public protocol SCLMBridgeMediaFilesModuleProtocol: class {
func openMediaFile(_ fileName: String)
func openMediaLibrary()
func showMediaLibraryBtn()
func hideMediaLibraryBtn()
}
SCLMBridgeMapModule
public protocol SCLMBridgeMapModuleProtocol: class {
func hideMapBtn()
func showMapBtn()
}
为了添加模块,需要实现添加的模块协议。
SCLMBridgeMediaFilesModule模块实现示例
class PresentationViewController: UIViewController, SCLMBridgeMediaFilesModuleProtocol {
// MARK: - SCLMBridgeMediaFilesModuleProtocol
func openMediaFile(_ fileName: String) {
DispatchQueue.main.async {
let mediaVC = MediaViewController.get()
mediaVC.inject(presentation: self.currentPresentation)
mediaVC.inject(bridge: self.bridge)
mediaVC.inject(mediaFileNameToOpenAtLaunch: fileName)
self.present(mediaVC, animated: true, completion: nil)
}
}
func openMediaLibrary() {
DispatchQueue.main.async {
let mediaVC = MediaViewController.get()
mediaVC.inject(presentation: self.currentPresentation)
mediaVC.inject(bridge: self.bridge)
self.present(mediaVC, animated: true, completion: nil)
}
}
func showMediaLibraryBtn() {
mediaButton.show()
}
func hideMediaLibraryBtn() {
mediaButton.hide()
}
}
创建自己的模块以处理命令
- 通过从
SCLMBridgeModule
继承创建模块类,并实现execute
方法。
import StoryContent
protocol CustomBridgeModuleDelegate: class {
func customBridgeModuleDelegateCallback(command: String, params: Any)
}
class CustomBridgeModule: SCLMBridgeModule {
struct Commands {
static let command1 = "Mycommand1"
static let command2 = "Mycommand2"
static var allCommands: [String] {
return [ command1, command2 ]
}
}
weak var delegate: CustomBridgeModuleDelegate?
override func execute(message: SCLMBridgeMessage, result: @escaping (SCLMBridgeResponse?) -> Void) {
switch message.command {
case Commands.command1:
result(commandHandler1(guid: message.guid, command:message.command, data: message.data))
case Commands.command2:
result(commandHandler2(guid: message.guid, command:message.command, data: message.data))
default:
result(SCLMBridgeResponse(guid: message.guid, responseData: nil, errorCode: .failure, errorMessage: "unknown command"))
}
}
private func commandHandler1(guid: String, command: String, data: Any) -> SCLMBridgeResponse {
// get some job here
delegate?.customBridgeModuleDelegateCallback(command: command, params: data)
return SCLMBridgeResponse(guid: guid, responseData: nil, errorCode: ResponseStatus.success, errorMessage: nil)
}
private func commandHandler2(guid: String, command: String, data: Any) -> SCLMBridgeResponse {
// get some job here
delegate?.customBridgeModuleDelegateCallback(command: command, params: data)
return SCLMBridgeResponse(guid: guid, responseData: nil, errorCode: ResponseStatus.success, errorMessage: nil)
}
}
- 注册新命令并将自己的模块添加到桥接器中
if let bridge = self.bridge {
let customBridgeModule = CustomBridgeModule(presenter: webView, session: bridge.sessions.session, presentation: currentPresentation, settings: nil, environments: nil, delegate: nil)
customBridgeModule.delegate = self
bridge.subscribe(module: customBridgeModule, toCommands: CustomBridgeModule.Commands.allCommands)
bridge.addBridgeModule(customBridgeModule)
}