CocoaPods менеджер зависимостей для Cocoa проектов.
Чтобы интегрировать StoryContent в ваш проект с помощью CocoaPods, укажите его в своем Podfile
:
pod 'StoryContent', '~> 0.5.0'
Если вы предпочитаете не использовать менеджеров зависимостей CocoaPods, вы можете добавить StoryContent.framework в свой проект вручную.
StoryContent состоит из следующих модулей:
- SCLMAuthService - аутентификация
- SCLMSyncService - доступ к RESTful API StoryCLM
- 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.auth(username: String,
password: String,
success: @escaping () -> Void,
failure: @escaping (SCLMError) -> Void)
При успешной аутентификации будет получен 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(completionHandler: @escaping (_ error: SCLMError?) -> Void)
Данный метод загрузит всех доступных клиентов и презентации для каждого клиента
Доступ к данным предоставляет NSFetchedResultsController
Можно использовать существующий NSFetchedResultsController с sectionNameKeyPath: "client.name"
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 {
guard let sections = viewModel.fetchedResultsController.sections else {
return 0
}
return sections.count
}
Количество объектов секции (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: Presentation,
completionHandler: @escaping (_ error: Error?) -> Void,
progressHandler: @escaping (_ progress: Progress) -> Void,
psnHandler: ((_ psn: PresentationSynchronizingNow) -> Void)?)
Для восстановления прогреса синхронизации при обновлении данных необходимо воспользоваться следующими инструментами:
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: Presentation,
completionHandler: @escaping (_ error: Error?) -> Void)
Аналитика реализована в библиотеке StoryIoT, подключение которой осуществляется через CocoaPods
pod 'StoryIoT', '~> 1.6'
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 - это библиотека, предоставляющая доступ к системным функциям (API) платформы Story из контента. Библиотека должна использоваться в HTML5 приложениях для Story. В других CLM системах, а также без Story данная библиотека работать не будет.
Основная задача библиотеки посылать сообщения в StoryBridge и обрабатывать входящие сообщения. Это часть технологии StoryBridge на стороне контента. Web приложение вызывает методы библиотеки, которая в свою очередь создает команду и посылает в нативную часть StoryBridge, после выполнения, клиентское приложение, используя мост, отправляет результат (команду) в WebView, где эту команду и данные перехватывает библиотека, которая в свою очередь вызывает callback. Таким образом, результат работы нативного кода возвращается в Web приложение. Web приложению не важно какой операционной системе принадлежит WebView, оно просто оперирует методами библиотеки. Тем самым приложение может одинаково работать на всех клиентах StoryContent независимо от операционной системы. Библиотека отвечает за взаимодействие на стороне Web приложения и является его частью. Библиотека имеет единую реализацию под все операционные системы.
SCLMBridgeModule - это часть технологии StoryBridge на стороне нативного кода, которая умеет принимать сообщения от WebView и контента, находить и запускать модули-обработчики и возвращать результат работы обратно в WebView. Данный модуль управляет процессом по доставке сообщений и отвечает за надежную их обработку.
public protocol SCLMBridgeBaseModuleProtocol: class {
func goToSlide(_ slide: Slide, with data: Any)
func getNavigationData() -> Any
}
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)
}
public protocol SCLMBridgeUIModuleProtocol: class {
func hideCloseBtn()
func hideSystemBtns()
}
// обрабатывает комманды
let commands = [command.httpget,
command.httppost,
command.httpput,
command.httpdelete]
public protocol SCLMBridgeSessionsModuleProtocol: class {
func setSessionComplete()
}
public protocol SCLMBridgeCustomEventsModuleProtocol: class {
func setEventKey(_ key: String, and value: Any)
}
public protocol SCLMBridgeMediaFilesModuleProtocol: class {
func openMediaFile(_ fileName: String)
func openMediaLibrary()
func showMediaLibraryBtn()
func hideMediaLibraryBtn()
}
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)
}