iOS Integration Guide
PhoenixSdk is an indoor positioning, mobile-ready, offline, Sdk, designed to be integrated into an existing mobile application effortlessly
- Ready to use
- Modular Features
- iOS Optimized
Features
- Localize user into indoor/outdoor area thanks to low-signal Bluetooth beacons and GPS
- Calculate user realtime position
- Monitoring indoor/outdoor switching and change floor
- Elaborate custom road to point of interest
You can also:
- Integrate this module into an existing app to customize UX
- Manipulate indoor/outdoor user’s position coordinate freely
Requirements
- Swift 5
- iOS 13.2
- xcode 12
Installation
Cocoapods
CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate PhoenixSdk into your Xcode project using CocoaPods, open your terminal:
(in your home directory)
1) gem install cocoapods-art
2) nano ~/.netrc
3) paste this in you .netrc file
machine nextome.jfrog.io
login <USERNAME>
password <ENCRYPTED-PASSWORD>
than close and save
3) pod repo-art add nextome-cocoapods-local "https://nextome.jfrog.io/artifactory/api/pods/nextome-cocoapods-local"
If you get an error of this type: "Permission bits, should be 0600, but are 644"
Run this command:
chmod 0600 ~/.netrc
Open your Podfile and add these lines:
After add this pod:
platform :ios, '13.0'
source 'https://github.com/CocoaPods/Specs.git'
plugin 'cocoapods-art', :sources => [
'nextome-cocoapods-local'
]
pod "PhoenixSdk", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/nextome-sdk.tar.gz', :type => 'tgz'
pod "NextomeLegacy", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/nextome-sdk.tar.gz', :type => 'tgz'
Then, into general tab, add this capabilities:
- Background Modes
- Uses Bluetooth LE accessories
- Location Updates
Finally, add this line to your Info.plist
- Privacy - Location Always and When In Use Usage Description
- Privacy - Location Always Usage Description
- Privacy - Location When In Use Usage Description
DONE
ONLY FOR MAP TEST PURPOSE
If you want only to test map, you can use x86 framework version. Remove previous integration lines and replace with these, into your podfile:
pod "PhoenixSdk_x86", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/nextome-sdk.tar.gz', :type => 'tgz'
pod "NextomeLegacy_x86", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/nextome-sdk.tar.gz', :type => 'tgz'
##Notice
-
Into iOS simulator, init Nextome Sdk with override mode; specifying venue and map (you will read more details as you go along), because Simulator doesn’t support Bluetooth.
-
Replace x86 pod with arm64 before send to store, because Apple rejects all type of project which contains third party library that integrates simulator architecture.
How it works
PhoenixSdk is currently design with public async observer
Info | Observer |
---|---|
User position | POSITION_STREAM |
Floor change | FLOOR_CHANGE |
Indoor/outdoor switch | OUTDOOR_STREAM |
General Log | LOG_WRITER |
Error | ERROR |
Getting Started
- Download Nextome.plist file and insert it, into your main’s app Target -> (You can download it from here: Nextome.plist).
- Insert auth access keys
- Insert bundle data and you can adjust settings* for sdk.
Settings Detail
Setting | Detail |
---|---|
Particle Engine | Correction of user position trajectory |
Position Log | Send last calculate user position to Nextome server, to improve sdk |
Mode | “BLE” localize user venue through beacon monitoring. |
“GPS” localize user venue through gps | |
Binaries | -Not implemented yet- |
Notice
You can modify these settings, easily calling NextomeSdk, public interface:
- sdkMode = “GPS” or “BLE”
- enableParticleEngine = true/false
- enableSendLog = true/false
- enableBinaries = true/false
- Import and initialize Sdk, into your AppDelegate
import PhoenixSdk
var sdk: NextomeSdk?
After didFinishLaunchingWithOptions
self.sdk = NextomeSdk(device: String? = nil, logTimer: Int? = nil, venueId: Int? = nil, floorId: Int? = nil, eventTimeout: Int? = nil)
Optional override parameter | Detail |
---|---|
device | Set custom device data |
logTimer | Customize send log to Nextome server timer |
venueId | Ovveride venue id and skipping localization |
floorId | Ovveride current map |
eventTimeout | Event Trigger delay |
## Notice If you specificy VenueId and FloorId, Sdk localization’s feature will turn off. NextomeSdk will only download maps and display theme.
Otherwise, if you want to init NextomeSdk with localization enabled; without customizations, you can easily do this:
self.sdk = NextomeSdk()
DONE
Bonus 1: User Position
If you need to show, user position on virtual map, follow this step to integrate compiled flutter framework into your project. Add these lines to your PodFile. Pay Attention: chose configuration TYPE = debug/release
pod "App_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
pod "Flutter_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
pod "FlutterPluginRegistrant_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
pod "FMDB_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
pod "path_provider_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
pod "sqflite_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
pod "compass_{TYPE}", :http => 'https://nextome.jfrog.io/artifactory/nextome-cocoapods-local/flutter-map.tar.gz', :type => 'tgz'
Bonus 2: Customize flutter map
By default, Sdk use Compiled Nextome Flutter Map cross-plattform module, developed to display user position and indoor map. If you want to customize it, with different features or design, follow this step to integrate, Nextome Flutter Map Source files.
-
Configure Flutter Sdk and prepare Android Studio with flutter plugins Flutter Get Started
-
Clone into your main project’s folder, Nextome Flutter Map respository Repository
-
Open flutter_mapview module with Android Studio and run it on iOS simulator. After finish, you’re now ready to integrate modules into iOS main App.
- Add to your PodFile
flutter_application_path = '{path}/flutter_mapview' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') install_all_flutter_pods(flutter_application_path)
Run pod install
- Into your AppDelegate import and init Flutter engine
import Flutter import FlutterPluginRegistrant
var flutterEngine : FlutterEngine?
After didFinishLaunchingWithOptions
self.flutterEngine = FlutterEngine(name: "io.flutter", project: nil) self.flutterEngine?.run(withEntrypoint: nil) GeneratedPluginRegistrant.register(with: self.flutterEngine!)
- Into your Main View Controller prepare flutter channel, to connect iOS and Flutter Engine.
import Flutter
var flutterViewController: FlutterViewController?
var flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine
var mapChannel = FlutterMethodChannel()
flutterViewController = FlutterViewController(engine: flutterEngine!, nibName: nil, bundle: nil)
self.mapChannel = FlutterMethodChannel(name: "net.nextome.phoenix", binaryMessenger: flutterViewController!.binaryMessenger)
DONE
Observers
Open your Main View Controller and into viewDidLoad, attach to main’s observers
//position observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceivePosition(_:)), name: NSNotification.Name(rawValue: "POSITION_STREAM"), object: nil)
//event observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveEventEnter(_:)), name: NSNotification.Name(rawValue: "EVENT_ENTER_STREAM"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveEventExit(_:)), name: NSNotification.Name(rawValue: "EVENT_EXIT_STREAM"), object: nil)
//floor observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveFloorChange(_:)), name: NSNotification.Name(rawValue: "FLOOR_CHANGE"), object: nil)
//path observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceivePath(_:)), name: NSNotification.Name(rawValue: "PATH_STREAM"), object: nil)
//outdoor observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveOutdoor(_:)), name: NSNotification.Name(rawValue: "OUTDOOR_STREAM"), object: nil)
(optional)
//log observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveLogs(_:)), name: NSNotification.Name(rawValue: "LOG_WRITER"), object: nil)
//error observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveError(_:)), name: NSNotification.Name(rawValue: "ERROR"), object: nil)
//Refresh Poi observer
NotificationCenter.default.addObserver(self, selector: #selector(onRefreshPoiComplete(_:)), name: NSNotification.Name(rawValue: "REFRESHPOI_COMPLETE"), object: nil)
Prepares for use, onDidReceive functions, to manage Sdk data.
onDidReceivePosition
@objc func onDidReceivePosition(_ notification: Notification){
//receive position
let positionData = notification.userInfo as? [String : [String: Any]]
//get data
let x = positionData!["positionData"]!["x"] as! String
let y = positionData!["positionData"]!["y"] as! String
let floor = positionData!["positionData"]!["floor"] as! String
//send to flutter engine, new position's data
self.mapChannel.invokeMethod("position", arguments: x + "," + y)
}
onDidReceiveEventEnter
@objc func onDidReceiveEventEnter(_ notification: Notification){
//receive event
let eventData = notification.userInfo as? [String : NextomeEvent]
let enterEvent = eventData!["eventData"]!
}
onDidReceiveEventExit
@objc func onDidReceiveEventExit(_ notification: Notification){
//receive event
let eventData = notification.userInfo as? [String : NextomeEvent]
let exitEvent = eventData!["eventData"]!
}
onDidReceivePath
//MARK: MAP Path observer
@objc func onDidReceivePath(_ notification: Notification){
//receive path
let pathData = notification.userInfo as? [String : String]
if(pathData!["pathData"] != nil){
if(pathData!["pathData"] == "[]"){
//advise ui
PKHUD.sharedHUD.contentView = PKHUDTextView(text: "Shortest path not found. Please try again")
PKHUD.sharedHUD.show()
PKHUD.sharedHUD.dimsBackground = false
PKHUD.sharedHUD.hide(afterDelay: 2) { success in
}
}else{
self.mapChannel.invokeMethod("path", arguments: pathData!["pathData"]!)
}
}
}
onDidReceiveOutdoor
@objc func onDidReceiveOutdoor(_ notification: Notification){
//receive position
let positionData = notification.userInfo as? [String : Double]
//get data
let lat = positionData!["lat"]!
let lng = positionData!["lng"]!
}
onDidReceiveFloorChange
@objc func onDidReceiveFloorChange(_ notification: Notification){
//receive position
let floorData = notification.userInfo as? [String : [String: Any]]
//update poi
let poiData = self.sdk?.getPOIData()
mapChannel.invokeMethod("POI", arguments: poiData)
//update map's package
let data = self.sdk?.getVenueData()
mapChannel.invokeMethod("localPackageUrl", arguments: data)
}
(optional)
onDidReceiveError
@objc func onDidReceiveError(_ notification: Notification){
//advise user
print("Errore. Riavvia l'sdk ")
}
onDidReceiveLogs
@objc func onDidReceiveLogs(_ notification: Notification){
//get data
let logInfo = notification.userInfo as? [String: String]
var log = logInfo!["log"]!
}
onRefreshPoiComplete
@objc func onRefreshPoiComplete(_ notification: Notification){
//get new data and send to flutter engine
let poiData = self.sdk?.getPOIData()
mapChannel.invokeMethod("POI", arguments: poiData)
}
Sdk Public Methods
Start
Start Sdk
sdk?.start()
Stop
Stop Sdk
sdk?.stop()
Venue data (Flutter Utils)
Get current venue package data to easily send it, to flutter engine Please note: this method returns data only if sdk state is Running
sdk?.getVenueData() -> String?
POI (Flutter Utils)
Get current venue POI data to easily send it, to flutter engine Please note: this method returns data only if sdk state is Running
sdk?.getPOIData() -> String?
Refresh POI (Flutter Utils)
Force Refresh current venue POI data to easily send it, to flutter engine
sdk?.refreshPOI()
Refresh Map
Force Refresh current displayed map Keep Attention: if you force mapId during localization, sdk locks forced map and not switchs anymore
sdk?.forceRefreshMap(mapId: Int)
To restore map’s switch, according to user position
sdk?.setLiveMap()
Venue Resources (Sdk v. >= 1.3.1)
Get resource bundle data, containing all Venue’s Pois and Maps. Please note: this method returns data only if sdk state is Running
sdk?.getVenueResources() -> VenueResources?
Way Finding
Calculate shortest route:
- from user position to selected map’s POI
- from custom position A to custom position B and send it to flutter engine
sdk?.calculatePath_ToPOI(poi: String)
sdk?.calculatePath_ToCustomPoint(point: String)
Position
Get current user position data
sdk?.getPositionData() -> [String: Any]
Report
send SDK Report to Nextome admin
sdk?.sendLogReport -> Void
Flutter Map
Easily show flutter_map module with current venue (localized place) data
//push flutter controller
self.navigationController?.pushViewController(flutterViewController!, animated: true)
//get map's poi
let poiData = self.sdk?.getPOIData()
mapChannel.invokeMethod("POI", arguments: poiData)
//get map's resources
let data = self.sdk?.getVenueData()
mapChannel.invokeMethod("localPackageUrl", arguments: data)
//flutter callbacks
mapChannel.setMethodCallHandler{(call: FlutterMethodCall, result: FlutterResult) -> Void in
// Handle poi json
if call.method == "poiData"{
//calculate path
self.sdk?.calculatePath(poi: call.arguments as? String)
}
// Handle long press
if(call.method == "customPosition"){
let customPosition = call.arguments as! String
//advise user
if(self.sdk?.customPositionCheck == 0){
//advise ui
PKHUD.sharedHUD.contentView = PKHUDTextView(text: "Start point is set")
PKHUD.sharedHUD.show()
PKHUD.sharedHUD.hide(afterDelay: 1.0) { success in
}
}
self.sdk?.calculateCustomPath(point: customPosition)
}
}
Customization
Show Center Position Fab
Show FAB on Map to enable custom camera feature to live follow user position
mapChannel.invokeMethod("showCenterPositionFab", arguments: "true")
Override user position icon
Change user position custom icon
mapChannel.invokeMethod("customPositionResourceUrl", arguments: "URL Png")
Example
You can clone this repository and explore our complete Example project, to see how to manage all Sdk’s data.
#Common issues
Redefinition of Module Minizip
If swift compiler report an error about redefenition of Module Minizip, you can fix in this way: 1) Click on “Previously defined here” gray text 2) Xcode will open a tab called module with some code 3) Delete all code and rebuild the project
App Distribution
If you have problem with app export from xcode or AppStore distribution, follow this steps:
1) Open your Xcode project’s setting 2) Click on Build Phase tab 3) Add new Run Script and paste this code, which remove all x86 files and architecture from project
# Type a script or drag a script file from your workspace to insert its path.
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
echo $(lipo -info "$FRAMEWORK_EXECUTABLE_PATH")
FRAMEWORK_TMP_PATH="$FRAMEWORK_EXECUTABLE_PATH-tmp"
case "${TARGET_BUILD_DIR}" in
*"iphonesimulator")
echo "No need to remove archs"
;;
*)
if $(lipo "$FRAMEWORK_EXECUTABLE_PATH" -verify_arch "i386") ; then
lipo -output "$FRAMEWORK_TMP_PATH" -remove "i386" "$FRAMEWORK_EXECUTABLE_PATH"
echo "i386 architecture removed"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_TMP_PATH" "$FRAMEWORK_EXECUTABLE_PATH"
fi
if $(lipo "$FRAMEWORK_EXECUTABLE_PATH" -verify_arch "x86_64") ; then
lipo -output "$FRAMEWORK_TMP_PATH" -remove "x86_64" "$FRAMEWORK_EXECUTABLE_PATH"
echo "x86_64 architecture removed"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_TMP_PATH" "$FRAMEWORK_EXECUTABLE_PATH"
fi
;;
esac
echo "Completed for executable $FRAMEWORK_EXECUTABLE_PATH"
echo $(lipo -info "$FRAMEWORK_EXECUTABLE_PATH")
done
Connect with us
https://www.nextome.net/
License
MIT
**© 2020 Nextome srl | All Rights Reserved.** |