インポート
import OneS1ght
#import "MyApp-Swift.h" // MyApp = Product Module Name
初期化
.initialize
OneS1ght API キーを検証し、テナント設定を取得します。測位エンジンの起動や端末の対応確認は行いません。try await OneS1ght.initialize(sdkKey: "YOUR_SDK_KEY")
[OneS1ghtBridge initializeWithSdkKey:@"YOUR_SDK_KEY" completion:^(NSError *error) { }];
.requestPermission
測位エンジンの利用に必要なアプリ権限をリクエストします。| 結果 | 意味 |
|---|---|
authorized | 測位可能 |
denied | 権限拒否 |
unsupported | 測位非対応端末 |
let status = await OneS1ght.requestPermission()
[OneS1ghtBridge requestPermission:^(NSString *status) { }]; // @"authorized" · @"denied" · @"unsupported"
.setLanguage
SDK のログ文言の言語を設定します。ko: 韓国語、 ja: 日本語、 en: 英語、 nil: システム設定の言語
OneS1ght.setLanguage("ja")
[OneS1ghtBridge setLanguage:@"ja"];
.onDebugLog
SDK のログを受信します。level: ログレベル、 message: ログメッセージ
OneS1ght.onDebugLog = { level, message in
if level >= .warn { print("[OneS1ght] \(message)") }
}
[OneS1ghtBridge onDebugLog:^(NSString *level, NSString *message) { }];
.reset
セッションを破棄します。以降の API はinitialize を再度呼ぶまで notInitialized エラーになります(別のキーでも可)。
await OneS1ght.reset()
[OneS1ghtBridge reset:^{ }];
状態の取得
.isInitialized
初期化に成功したかを返します。let ready = OneS1ght.isInitialized
BOOL ready = OneS1ghtBridge.isInitialized;
.deviceAvailability
測位可能な端末かを判定し、非対応の場合はその理由を返します。| 値 | 意味 |
|---|---|
available | 測位可能 |
osVersionTooLow | iOS 27 未満 |
deviceNotSupported | UWB 非対応端末 |
switch OneS1ght.deviceAvailability {
case .available: break
case .osVersionTooLow: break
case .deviceNotSupported: break
@unknown default: break
}
NSString *availability = OneS1ghtBridge.deviceAvailability; // @"available" · @"osVersionTooLow" · @"deviceNotSupported"
.googleMapKey
図面を表示するための Google Maps API キーを返します。let mapKey = OneS1ght.googleMapKey // String?
NSString *mapKey = OneS1ghtBridge.googleMapKey;
.sdkVersion
使用中の SDK バージョンを返します。let version = OneS1ght.sdkVersion // "0.2.3"
NSString *version = OneS1ghtBridge.sdkVersion;
プロフィール
.createProfile
新しいパーソナライズプロフィールを作成します。成功すると一意のプロフィール ID を返します。let profileId = try await OneS1ght.createProfile(["gender": "F", "ageBand": "20s"])
[OneS1ghtBridge createProfile:@{ @"gender": @"F", @"ageBand": @"20s" }
completion:^(NSString *profileId, NSError *error) { }];
.identify
収集する測位データを紐付けるプロフィールを設定します。解除する場合はnil を設定してください。
OneS1ght.identify(profileId: profileId)
[OneS1ghtBridge identifyWithProfileId:profileId];
.fetchProfile
プロフィールの属性を取得します。let attributes = try await OneS1ght.fetchProfile(profileId)
[OneS1ghtBridge fetchProfile:profileId
completion:^(NSDictionary<NSString *, NSString *> *attributes, NSError *error) { }];
.replaceProfile
プロフィールの属性を置き換えます。以前のデータは削除されます。try await OneS1ght.replaceProfile(profileId, attributes: ["gender": "F", "ageBand": "30s"])
[OneS1ghtBridge replaceProfile:profileId
attributes:@{ @"gender": @"F", @"ageBand": @"30s" }
completion:^(NSError *error) { }];
.deleteProfile
プロフィール情報を完全に削除します。try await OneS1ght.deleteProfile(profileId)
[OneS1ghtBridge deleteProfile:profileId completion:^(NSError *error) { }];
空間の取得
.buildings
テナントに紐付くすべての建物一覧を取得します。let buildings = try await OneS1ght.buildings()
let building = try await OneS1ght.building(id: buildingId)
[OneS1ghtBridge buildings:^(NSArray<NSDictionary *> *buildings, NSError *error) { }]; // id · name · floorCount
.floors
建物のフロア一覧を取得します。let floors = try await OneS1ght.floors(buildingId: buildingId)
[OneS1ghtBridge floorsWithBuildingId:buildingId
completion:^(NSArray<NSDictionary *> *floors, NSError *error) { }];
.floor
選択したフロアの詳細情報を取得します。let floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
let png = floor.image // Data?
[OneS1ghtBridge floorWithBuildingId:buildingId floorId:floorId
completion:^(NSDictionary *floor, NSError *error) {
NSData *png = floor[@"image"];
}];
.zones
選択したフロアのすべてのゾーンを取得します。let zones = try await OneS1ght.zones(buildingId: buildingId, floorId: floorId)
let zone = try await OneS1ght.zone(buildingId: buildingId, floorId: floorId, zoneId: zoneId)
[OneS1ghtBridge zonesWithBuildingId:buildingId floorId:floorId
completion:^(NSArray<NSDictionary *> *zones, NSError *error) { }]; // id · name · polygon · dwellSeconds
.locators
選択したフロアのロケーター情報を取得します。let floorLocators = try await OneS1ght.locators(buildingId: buildingId, floorId: floorId)
let ready = floorLocators.positioningReady
[OneS1ghtBridge locatorsWithBuildingId:buildingId floorId:floorId
completion:^(NSDictionary *floorLocators, NSError *error) { }]; // locators · sessionId
フロアの指定
.setFloorMap
測位を開始するフロアを指定します。let floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
[OneS1ghtBridge setFloorMapWithBuildingId:buildingId floorId:floorId
completion:^(NSError *error) { }]; // floorId nil = 解除
.refreshZones
最新のゾーン情報を取得します。let zones = await OneS1ght.refreshZones()
[OneS1ghtBridge refreshZones:^(NSArray<NSDictionary *> *zones) { }];
測位セッション
.floorSession
測位セッションを取得します。フロアに関係なく、アプリ全体で同じインスタンス(シングルトン)です。let session = try OneS1ght.floorSession()
// ブリッジがセッションを内包します — 個別に取得しません。
.begin
ユーザーの測位を開始します。try await session.begin()
[OneS1ghtBridge beginWithCompletion:^(NSError *error) { }];
.end
ユーザーの測位を終了します。await session.end()
[OneS1ghtBridge endWithCompletion:^{ }];
.pause · .resume
ユーザーの測位を一時停止・再開します。終了とは異なり、SDK 内の測位エンジンは停止しません。session.pause()
session.resume()
[OneS1ghtBridge pause];
[OneS1ghtBridge resume];
.isRunning · .isPaused
測位中かどうかと、一時停止中かどうかを返します。let running = session.isRunning
let paused = session.isPaused
let current = session.floor // Floor?
BOOL running = OneS1ghtBridge.isRunning;
BOOL paused = OneS1ghtBridge.isPaused;
セッションイベント
.onPosition
ユーザーの座標をリアルタイムで返すイベントが発生します。エンジンの頻度のまま届き、毎秒 4 回(デフォルト)はサーバー送信分にのみ適用されます。session.onPosition = { coordinates in print(coordinates.x, coordinates.y) }
[OneS1ghtBridge onPosition:^(double x, double y, double z) { }];
.onZoneEnter · .onZoneExit
ゾーンへの進入・退出時にイベントが発生します。session.onZoneEnter = { zone in print("IN \(zone.name)") }
session.onZoneExit = { zone in print("OUT \(zone.name)") }
[OneS1ghtBridge onZoneEnter:^(NSDictionary *zone) { }];
[OneS1ghtBridge onZoneExit:^(NSDictionary *zone) { }];
.onZoneDwell
特定ゾーンの滞在時間が基準(dwellSeconds)を超えるとイベントが発生します。
session.onZoneDwell = { zone, seconds in print("DWELL \(zone.name) \(seconds)s") }
[OneS1ghtBridge onZoneDwell:^(NSDictionary *zone, double seconds) { }];
.onTriggers
空間内のイベント(施策)が動作するとイベントが発生します。session.onTriggers = { zoneId, triggers in
for trigger in triggers { print(trigger.type, trigger.payload ?? [:]) }
}
[OneS1ghtBridge onTriggers:^(NSString *zoneId, NSArray<NSDictionary *> *triggers) { }]; // triggerId · type · payload
.onFloorDetected
ロケーターの BLE 信号でフロアを見つけるとフロア ID で、フロアを見失うとnil でイベントが発生します。
session.onFloorDetected = { floorId in
guard let floorId else { return }
Task {
let floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
}
}
[OneS1ghtBridge onFloorDetected:^(NSString *floorId) {
if (!floorId) { return; }
[OneS1ghtBridge setFloorMapWithBuildingId:buildingId floorId:floorId completion:^(NSError *error) { }];
}];
.onStopped
ユーザーの測位が停止するとイベントが発生します。| 理由 | 意味 |
|---|---|
ended | end()・reset・キーの変更 |
engineFailed | 測位エンジンの起動失敗 |
session.onStopped = { reason in
switch reason {
case .ended: break
case .engineFailed: break
@unknown default: break
}
}
[OneS1ghtBridge onStopped:^(NSString *reason) { }]; // @"ended" · @"engineFailed"
.onConfigChanged
コンソール画面で設定が変更されるとイベントが発生します。| シグナル | 意味 |
|---|---|
zonesChanged(floorId) | ゾーン変更 |
planChanged(floorId) | 図面変更 |
rulesChanged(zoneId) | 施策変更 |
sdkConfigChanged | リモート設定変更 |
resyncNeeded | 再接続・イベントの取りこぼし |
session.onConfigChanged = { change in
switch change {
case .zonesChanged, .resyncNeeded: Task { _ = await OneS1ght.refreshZones() }
case .planChanged(let floorId): print("redraw", floorId ?? "")
default: break
}
}
[OneS1ghtBridge onConfigChanged:^(NSString *kind, NSString *targetId) {
if ([kind isEqualToString:@"zonesChanged"]) {
[OneS1ghtBridge refreshZones:^(NSArray<NSDictionary *> *zones) { }];
}
}];
データ送信
座標は 300 件または 60 秒ごと、およびバックグラウンド移行時とend() 時に自動で送信されます。
.uploadPendingPositions
溜めた座標を即時送信します。await OneS1ght.uploadPendingPositions()
[OneS1ghtBridge uploadPendingPositions:^{ }];
.discardPendingPositions
溜めた座標を破棄します。OneS1ght.discardPendingPositions()
[OneS1ghtBridge discardPendingPositions];
エラー処理
失敗はSdkError(SDK の状態)と ApiError(通信)で届きます。コードの意味はエラーコードを参照してください。Objective-C では NSError.userInfo[@"sdkCode"] に入ります。
do {
try await OneS1ght.initialize(sdkKey: key)
} catch let e as SdkError {
print(e.code.rawValue) // "E1003"
} catch let e as ApiError {
print(e.code.rawValue, e.description) // "E5005" · 理由
}
[OneS1ghtBridge initializeWithSdkKey:key completion:^(NSError *error) {
NSString *code = error.userInfo[@"sdkCode"]; // @"E1003"
}];
モデル
リストは[T]、バイト列は Data です。
Coordinates
| フィールド | 型 | 説明 |
|---|---|---|
x・y | Double | 図面ローカルのメートル |
z | Double | 高さ |
Building · Floor
| 型 | フィールド | 説明 |
|---|---|---|
Building | id・name | 建物 ID・名前 |
floorCount: Int? | フロア数 | |
Floor | id・name | フロア ID・名前 |
image: Data? | 図面の PNG バイト | |
hasPlan: Bool | 図面登録の有無 | |
originX・originY | 図面原点(メートル) | |
widthM・heightM | 図面サイズ(メートル) | |
minX・minY・maxX・maxY | 配置範囲 |
Locator · FloorLocators
| 型 | フィールド | 説明 |
|---|---|---|
Locator | address: Int | UWB MAC の下位 2 バイト |
x・y・z | 図面ローカルのメートル | |
FloorLocators | locators | フロアのロケーター一覧 |
sessionId: Int? | UWB セッション — フロアごとに異なる | |
positioningReady: Bool | ロケーターとセッションの両方があるか |
Zone
| フィールド | 型 | 説明 |
|---|---|---|
id・name | String | ゾーン ID・名前 |
polygon | [Position] | 頂点(x、y メートル) |
dwellSeconds | Int? | 滞在イベントの基準(秒) |
Trigger
| フィールド | 型 | 説明 |
|---|---|---|
triggerId | String | トリガー ID |
type | String | signage・coupon・tracking・merch・generic |
payload | [String: String]? | 種類ごとの付加情報 |
Objective-C ブリッジ
以下のファイルをアプリターゲットに追加して 1 回ビルドすると、<Product Module Name>-Swift.h が生成されます。
OneS1ghtBridge.swift
// OneS1ghtBridge.swift: Objective-C から OneS1ght を呼ぶためのブリッジ
import Foundation
import OneS1ght
@MainActor @objc public final class OneS1ghtBridge: NSObject {
// MARK: 初期化
@objc public static func initialize(sdkKey: String, completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.initialize(sdkKey: sdkKey); completion(nil) }
catch { completion(nsError(error)) }
}
}
/// status: "authorized" | "denied" | "unsupported"
@objc public static func requestPermission(_ completion: @escaping (String) -> Void) {
Task { completion(String(describing: await OneS1ght.requestPermission())) }
}
@objc public static func setLanguage(_ code: String?) {
OneS1ght.setLanguage(code)
}
@objc public static func reset(_ completion: @escaping () -> Void) {
Task { await OneS1ght.reset(); completion() }
}
/// level: "log" | "info" | "warn" | "error"
@objc public static func onDebugLog(_ handler: @escaping (String, String) -> Void) {
OneS1ght.onDebugLog = { level, message in handler(String(describing: level), message) }
}
// MARK: 状態
@objc public static var isInitialized: Bool { OneS1ght.isInitialized }
/// "available" | "osVersionTooLow" | "deviceNotSupported"
@objc public static var deviceAvailability: String { String(describing: OneS1ght.deviceAvailability) }
@objc public static var isDeviceAvailable: Bool { OneS1ght.isDeviceAvailable }
@objc public static var googleMapKey: String? { OneS1ght.googleMapKey }
@objc public static var sdkVersion: String { OneS1ght.sdkVersion }
// MARK: プロフィール
@objc public static func createProfile(_ attributes: [String: String],
completion: @escaping (String?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.createProfile(attributes), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func fetchProfile(_ profileId: String,
completion: @escaping ([String: String]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.fetchProfile(profileId), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func replaceProfile(_ profileId: String, attributes: [String: String],
completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.replaceProfile(profileId, attributes: attributes); completion(nil) }
catch { completion(nsError(error)) }
}
}
@objc public static func deleteProfile(_ profileId: String, completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.deleteProfile(profileId); completion(nil) }
catch { completion(nsError(error)) }
}
}
@objc public static func identify(profileId: String?) {
OneS1ght.identify(profileId: profileId)
}
// MARK: 空間の取得 — モデルは NSDictionary で渡す
@objc public static func buildings(_ completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.buildings().map(dict), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func building(id: String, completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do { completion(dict(try await OneS1ght.building(id: id)), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func floors(buildingId: String,
completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.floors(buildingId: buildingId).map(dict), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func floor(buildingId: String, floorId: String,
completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do { completion(dict(try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func zones(buildingId: String, floorId: String,
completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
Task {
do { completion(try await OneS1ght.zones(buildingId: buildingId, floorId: floorId).map(dict), nil) }
catch { completion(nil, nsError(error)) }
}
}
@objc public static func zone(buildingId: String, floorId: String, zoneId: String,
completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do {
let zone = try await OneS1ght.zone(buildingId: buildingId, floorId: floorId, zoneId: zoneId)
completion(dict(zone), nil)
} catch { completion(nil, nsError(error)) }
}
}
@objc public static func locators(buildingId: String, floorId: String,
completion: @escaping ([String: Any]?, NSError?) -> Void) {
Task {
do { completion(dict(try await OneS1ght.locators(buildingId: buildingId, floorId: floorId)), nil) }
catch { completion(nil, nsError(error)) }
}
}
// MARK: フロアの指定
/// floorId が nil ならフロア指定を解除する
@objc public static func setFloorMap(buildingId: String?, floorId: String?,
completion: @escaping (NSError?) -> Void) {
Task {
do {
var floor: Floor?
if let buildingId, let floorId {
floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
}
try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
completion(nil)
} catch { completion(nsError(error)) }
}
}
@objc public static func refreshZones(_ completion: @escaping ([[String: Any]]) -> Void) {
Task { completion(await OneS1ght.refreshZones().map(dict)) }
}
// MARK: 測位セッション
@objc public static func begin(completion: @escaping (NSError?) -> Void) {
Task {
do { try await OneS1ght.floorSession().begin(); completion(nil) }
catch { completion(nsError(error)) }
}
}
@objc public static func end(completion: @escaping () -> Void) {
Task {
if let session = try? OneS1ght.floorSession() { await session.end() }
completion()
}
}
@objc public static func pause() { (try? OneS1ght.floorSession())?.pause() }
@objc public static func resume() { (try? OneS1ght.floorSession())?.resume() }
@objc public static var isRunning: Bool { (try? OneS1ght.floorSession())?.isRunning ?? false }
@objc public static var isPaused: Bool { (try? OneS1ght.floorSession())?.isPaused ?? false }
// MARK: セッションイベント
/// 図面ローカルのメートル
@objc public static func onPosition(_ handler: @escaping (Double, Double, Double) -> Void) {
(try? OneS1ght.floorSession())?.onPosition = { c in handler(c.x, c.y, c.z) }
}
@objc public static func onZoneEnter(_ handler: @escaping ([String: Any]) -> Void) {
(try? OneS1ght.floorSession())?.onZoneEnter = { zone in handler(dict(zone)) }
}
@objc public static func onZoneExit(_ handler: @escaping ([String: Any]) -> Void) {
(try? OneS1ght.floorSession())?.onZoneExit = { zone in handler(dict(zone)) }
}
@objc public static func onZoneDwell(_ handler: @escaping ([String: Any], Double) -> Void) {
(try? OneS1ght.floorSession())?.onZoneDwell = { zone, seconds in handler(dict(zone), seconds) }
}
/// triggers: [{ triggerId, type, payload }]
@objc public static func onTriggers(_ handler: @escaping (String, [[String: Any]]) -> Void) {
(try? OneS1ght.floorSession())?.onTriggers = { zoneId, triggers in
handler(zoneId, triggers.map { t in
var d: [String: Any] = ["triggerId": t.triggerId, "type": t.type]
if let payload = t.payload { d["payload"] = payload }
return d
})
}
}
@objc public static func onFloorDetected(_ handler: @escaping (String?) -> Void) {
(try? OneS1ght.floorSession())?.onFloorDetected = { floorId in handler(floorId) }
}
/// reason: "ended" | "engineFailed"
@objc public static func onStopped(_ handler: @escaping (String) -> Void) {
(try? OneS1ght.floorSession())?.onStopped = { reason in handler(String(describing: reason)) }
}
/// kind: "zonesChanged" | "planChanged" | "rulesChanged" | "sdkConfigChanged" | "resyncNeeded"
/// targetId: zonesChanged・planChanged はフロア ID、rulesChanged はゾーン ID
@objc public static func onConfigChanged(_ handler: @escaping (String, String?) -> Void) {
(try? OneS1ght.floorSession())?.onConfigChanged = { change in
switch change {
case .zonesChanged(let floorId): handler("zonesChanged", floorId)
case .planChanged(let floorId): handler("planChanged", floorId)
case .rulesChanged(let zoneId): handler("rulesChanged", zoneId)
case .sdkConfigChanged: handler("sdkConfigChanged", nil)
case .resyncNeeded: handler("resyncNeeded", nil)
@unknown default: handler("unknown", nil)
}
}
}
// MARK: データ送信
@objc public static func uploadPendingPositions(_ completion: @escaping () -> Void) {
Task { await OneS1ght.uploadPendingPositions(); completion() }
}
@objc public static func discardPendingPositions() {
OneS1ght.discardPendingPositions()
}
// MARK: 変換
private static func dict(_ b: Building) -> [String: Any] {
var d: [String: Any] = ["id": b.id, "name": b.name]
if let count = b.floorCount { d["floorCount"] = count }
return d
}
private static func dict(_ f: Floor) -> [String: Any] {
var d: [String: Any] = ["id": f.id, "name": f.name, "hasPlan": f.hasPlan,
"originX": f.originX, "originY": f.originY,
"widthM": f.widthM, "heightM": f.heightM]
if let image = f.image { d["image"] = image }
return d
}
private static func dict(_ z: Zone) -> [String: Any] {
var d: [String: Any] = ["id": z.id, "name": z.name,
"polygon": z.polygon.map { [$0.x, $0.y] }]
if let dwell = z.dwellSeconds { d["dwellSeconds"] = dwell }
return d
}
private static func dict(_ l: FloorLocators) -> [String: Any] {
var d: [String: Any] = ["locators": l.locators.map {
["address": $0.address, "x": $0.x, "y": $0.y, "z": $0.z] as [String: Any]
}]
if let sessionId = l.sessionId { d["sessionId"] = sessionId }
return d
}
/// Swift のエラー → NSError。domain "OneS1ght"、code 3001、userInfo["sdkCode"] = "E3001" の形
private static func nsError(_ error: Error) -> NSError {
let code: SdkErrorCode
let message: String
switch error {
case let e as SdkError: code = e.code; message = e.code.summary
case let e as ApiError: code = e.code; message = e.description
default: return error as NSError
}
return NSError(domain: "OneS1ght",
code: Int(code.rawValue.dropFirst()) ?? 0,
userInfo: [NSLocalizedDescriptionKey: message,
"sdkCode": code.rawValue])
}
}
制約
- UWB 測位はフォアグラウンド専用 — バックグラウンドで停止し、復帰すると測位が再開されます。
- 座標バッファはメモリ上 — 強制終了すると未送信の座標は失われます。
- シミュレーターでは測位できません。