├── .gitignore ├── imageTransfer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── imageTransfer ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Camera_iOS_1024_1024_gradient.png │ │ ├── Contents.json │ │ ├── Icon-60.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x-1.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon-Spotlight-40.png │ │ ├── Icon-Spotlight-40@2x-1.png │ │ ├── Icon-Spotlight-40@2x.png │ │ ├── Icon-Spotlight-40@3x.png │ │ ├── Icon-Spotlight-41.png │ │ ├── Icon-Spotlight-42.png │ │ └── Icon-iPadPro@2x.png │ ├── By_Nordic.imageset │ │ ├── Contents.json │ │ ├── by_nordic.png │ │ ├── by_nordic@2x.png │ │ └── by_nordic@3x.png │ ├── Contents.json │ ├── companyLogo.imageset │ │ ├── Contents.json │ │ └── companyLogo-1.png │ ├── ic_bluetooth_disabled_white.imageset │ │ ├── Contents.json │ │ ├── ic_bluetooth_disabled_white.png │ │ ├── ic_bluetooth_disabled_white_2x.png │ │ └── ic_bluetooth_disabled_white_3x.png │ └── scanning.imageset │ │ ├── Contents.json │ │ ├── scanning.png │ │ ├── scanning@2x.png │ │ └── scanning@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Bluetooth │ ├── BluetoothManager.swift │ ├── BluetoothManagerDelegate.swift │ ├── CameraPeripheral.swift │ └── CameraPeripheralDelegate.swift ├── Info.plist └── ViewControllers │ ├── CameraViewController.swift │ ├── MainViewController.swift │ ├── RootNavigationViewController.swift │ └── SectionTableViewCell.swift └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /imageTransfer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3034F85B1F7BD46600DA8C88 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F85A1F7BD46600DA8C88 /* AppDelegate.swift */; }; 11 | 3034F8601F7BD46600DA8C88 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3034F85E1F7BD46600DA8C88 /* Main.storyboard */; }; 12 | 3034F8621F7BD46600DA8C88 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3034F8611F7BD46600DA8C88 /* Assets.xcassets */; }; 13 | 3034F8651F7BD46600DA8C88 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3034F8631F7BD46600DA8C88 /* LaunchScreen.storyboard */; }; 14 | 3034F86E1F7BD74C00DA8C88 /* BluetoothManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F86D1F7BD74C00DA8C88 /* BluetoothManager.swift */; }; 15 | 3034F8731F7BD90200DA8C88 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F8721F7BD90200DA8C88 /* MainViewController.swift */; }; 16 | 3034F8771F7BF00600DA8C88 /* CameraPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F8761F7BF00600DA8C88 /* CameraPeripheral.swift */; }; 17 | 3034F8791F7CE1A700DA8C88 /* BluetoothManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F8781F7CE1A700DA8C88 /* BluetoothManagerDelegate.swift */; }; 18 | 3034F87B1F7CE3EE00DA8C88 /* CameraPeripheralDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F87A1F7CE3EE00DA8C88 /* CameraPeripheralDelegate.swift */; }; 19 | 3034F87D1F7CE84500DA8C88 /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3034F87C1F7CE84500DA8C88 /* CameraViewController.swift */; }; 20 | 304F42E81FE11EB10010A446 /* RootNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304F42E71FE11EB10010A446 /* RootNavigationViewController.swift */; }; 21 | 521A71611F8FBEB90000EC48 /* SectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 521A71601F8FBEB90000EC48 /* SectionTableViewCell.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 3034F8571F7BD46600DA8C88 /* imageTransfer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = imageTransfer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 3034F85A1F7BD46600DA8C88 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | 3034F85F1F7BD46600DA8C88 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | 3034F8611F7BD46600DA8C88 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 3034F8641F7BD46600DA8C88 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 30 | 3034F8661F7BD46600DA8C88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 3034F86D1F7BD74C00DA8C88 /* BluetoothManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothManager.swift; sourceTree = ""; }; 32 | 3034F8721F7BD90200DA8C88 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 33 | 3034F8761F7BF00600DA8C88 /* CameraPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPeripheral.swift; sourceTree = ""; }; 34 | 3034F8781F7CE1A700DA8C88 /* BluetoothManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothManagerDelegate.swift; sourceTree = ""; }; 35 | 3034F87A1F7CE3EE00DA8C88 /* CameraPeripheralDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPeripheralDelegate.swift; sourceTree = ""; }; 36 | 3034F87C1F7CE84500DA8C88 /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = ""; }; 37 | 304F42E71FE11EB10010A446 /* RootNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationViewController.swift; sourceTree = ""; }; 38 | 521A71601F8FBEB90000EC48 /* SectionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionTableViewCell.swift; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 3034F8541F7BD46600DA8C88 /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 3034F84E1F7BD46600DA8C88 = { 53 | isa = PBXGroup; 54 | children = ( 55 | 3034F8591F7BD46600DA8C88 /* imageTransfer */, 56 | 3034F8581F7BD46600DA8C88 /* Products */, 57 | ); 58 | sourceTree = ""; 59 | }; 60 | 3034F8581F7BD46600DA8C88 /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 3034F8571F7BD46600DA8C88 /* imageTransfer.app */, 64 | ); 65 | name = Products; 66 | sourceTree = ""; 67 | }; 68 | 3034F8591F7BD46600DA8C88 /* imageTransfer */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 3034F86F1F7BD8D900DA8C88 /* ViewControllers */, 72 | 3034F86C1F7BD71A00DA8C88 /* Bluetooth */, 73 | 3034F85A1F7BD46600DA8C88 /* AppDelegate.swift */, 74 | 3034F85E1F7BD46600DA8C88 /* Main.storyboard */, 75 | 3034F8611F7BD46600DA8C88 /* Assets.xcassets */, 76 | 3034F8631F7BD46600DA8C88 /* LaunchScreen.storyboard */, 77 | 3034F8661F7BD46600DA8C88 /* Info.plist */, 78 | ); 79 | path = imageTransfer; 80 | sourceTree = ""; 81 | }; 82 | 3034F86C1F7BD71A00DA8C88 /* Bluetooth */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 3034F86D1F7BD74C00DA8C88 /* BluetoothManager.swift */, 86 | 3034F8781F7CE1A700DA8C88 /* BluetoothManagerDelegate.swift */, 87 | 3034F8761F7BF00600DA8C88 /* CameraPeripheral.swift */, 88 | 3034F87A1F7CE3EE00DA8C88 /* CameraPeripheralDelegate.swift */, 89 | ); 90 | path = Bluetooth; 91 | sourceTree = ""; 92 | }; 93 | 3034F86F1F7BD8D900DA8C88 /* ViewControllers */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 3034F8721F7BD90200DA8C88 /* MainViewController.swift */, 97 | 3034F87C1F7CE84500DA8C88 /* CameraViewController.swift */, 98 | 521A71601F8FBEB90000EC48 /* SectionTableViewCell.swift */, 99 | 304F42E71FE11EB10010A446 /* RootNavigationViewController.swift */, 100 | ); 101 | path = ViewControllers; 102 | sourceTree = ""; 103 | }; 104 | /* End PBXGroup section */ 105 | 106 | /* Begin PBXNativeTarget section */ 107 | 3034F8561F7BD46600DA8C88 /* imageTransfer */ = { 108 | isa = PBXNativeTarget; 109 | buildConfigurationList = 3034F8691F7BD46600DA8C88 /* Build configuration list for PBXNativeTarget "imageTransfer" */; 110 | buildPhases = ( 111 | 3034F8531F7BD46600DA8C88 /* Sources */, 112 | 3034F8541F7BD46600DA8C88 /* Frameworks */, 113 | 3034F8551F7BD46600DA8C88 /* Resources */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = imageTransfer; 120 | productName = imageTransfer; 121 | productReference = 3034F8571F7BD46600DA8C88 /* imageTransfer.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 3034F84F1F7BD46600DA8C88 /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastSwiftUpdateCheck = 0900; 131 | LastUpgradeCheck = 0900; 132 | ORGANIZATIONNAME = "Nordic Semiconductor ASA"; 133 | TargetAttributes = { 134 | 3034F8561F7BD46600DA8C88 = { 135 | CreatedOnToolsVersion = 9.0; 136 | ProvisioningStyle = Automatic; 137 | }; 138 | }; 139 | }; 140 | buildConfigurationList = 3034F8521F7BD46600DA8C88 /* Build configuration list for PBXProject "imageTransfer" */; 141 | compatibilityVersion = "Xcode 8.0"; 142 | developmentRegion = en; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | en, 146 | Base, 147 | ); 148 | mainGroup = 3034F84E1F7BD46600DA8C88; 149 | productRefGroup = 3034F8581F7BD46600DA8C88 /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | 3034F8561F7BD46600DA8C88 /* imageTransfer */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | 3034F8551F7BD46600DA8C88 /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | 3034F8651F7BD46600DA8C88 /* LaunchScreen.storyboard in Resources */, 164 | 3034F8621F7BD46600DA8C88 /* Assets.xcassets in Resources */, 165 | 3034F8601F7BD46600DA8C88 /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | 3034F8531F7BD46600DA8C88 /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 3034F8771F7BF00600DA8C88 /* CameraPeripheral.swift in Sources */, 177 | 3034F87B1F7CE3EE00DA8C88 /* CameraPeripheralDelegate.swift in Sources */, 178 | 3034F8791F7CE1A700DA8C88 /* BluetoothManagerDelegate.swift in Sources */, 179 | 304F42E81FE11EB10010A446 /* RootNavigationViewController.swift in Sources */, 180 | 3034F8731F7BD90200DA8C88 /* MainViewController.swift in Sources */, 181 | 3034F86E1F7BD74C00DA8C88 /* BluetoothManager.swift in Sources */, 182 | 521A71611F8FBEB90000EC48 /* SectionTableViewCell.swift in Sources */, 183 | 3034F87D1F7CE84500DA8C88 /* CameraViewController.swift in Sources */, 184 | 3034F85B1F7BD46600DA8C88 /* AppDelegate.swift in Sources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXSourcesBuildPhase section */ 189 | 190 | /* Begin PBXVariantGroup section */ 191 | 3034F85E1F7BD46600DA8C88 /* Main.storyboard */ = { 192 | isa = PBXVariantGroup; 193 | children = ( 194 | 3034F85F1F7BD46600DA8C88 /* Base */, 195 | ); 196 | name = Main.storyboard; 197 | sourceTree = ""; 198 | }; 199 | 3034F8631F7BD46600DA8C88 /* LaunchScreen.storyboard */ = { 200 | isa = PBXVariantGroup; 201 | children = ( 202 | 3034F8641F7BD46600DA8C88 /* Base */, 203 | ); 204 | name = LaunchScreen.storyboard; 205 | sourceTree = ""; 206 | }; 207 | /* End PBXVariantGroup section */ 208 | 209 | /* Begin XCBuildConfiguration section */ 210 | 3034F8671F7BD46600DA8C88 /* Debug */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_ANALYZER_NONNULL = YES; 215 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_COMMA = YES; 223 | CLANG_WARN_CONSTANT_CONVERSION = YES; 224 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 225 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 226 | CLANG_WARN_EMPTY_BODY = YES; 227 | CLANG_WARN_ENUM_CONVERSION = YES; 228 | CLANG_WARN_INFINITE_RECURSION = YES; 229 | CLANG_WARN_INT_CONVERSION = YES; 230 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 231 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 232 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 233 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 234 | CLANG_WARN_STRICT_PROTOTYPES = YES; 235 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 236 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | CODE_SIGN_IDENTITY = "iPhone Developer"; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = dwarf; 242 | ENABLE_STRICT_OBJC_MSGSEND = YES; 243 | ENABLE_TESTABILITY = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu11; 245 | GCC_DYNAMIC_NO_PIC = NO; 246 | GCC_NO_COMMON_BLOCKS = YES; 247 | GCC_OPTIMIZATION_LEVEL = 0; 248 | GCC_PREPROCESSOR_DEFINITIONS = ( 249 | "DEBUG=1", 250 | "$(inherited)", 251 | ); 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 259 | MTL_ENABLE_DEBUG_INFO = YES; 260 | ONLY_ACTIVE_ARCH = YES; 261 | SDKROOT = iphoneos; 262 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 263 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 264 | }; 265 | name = Debug; 266 | }; 267 | 3034F8681F7BD46600DA8C88 /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 278 | CLANG_WARN_BOOL_CONVERSION = YES; 279 | CLANG_WARN_COMMA = YES; 280 | CLANG_WARN_CONSTANT_CONVERSION = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 283 | CLANG_WARN_EMPTY_BODY = YES; 284 | CLANG_WARN_ENUM_CONVERSION = YES; 285 | CLANG_WARN_INFINITE_RECURSION = YES; 286 | CLANG_WARN_INT_CONVERSION = YES; 287 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 291 | CLANG_WARN_STRICT_PROTOTYPES = YES; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | CODE_SIGN_IDENTITY = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu11; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 310 | MTL_ENABLE_DEBUG_INFO = NO; 311 | SDKROOT = iphoneos; 312 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 313 | VALIDATE_PRODUCT = YES; 314 | }; 315 | name = Release; 316 | }; 317 | 3034F86A1F7BD46600DA8C88 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 321 | CODE_SIGN_STYLE = Automatic; 322 | DEVELOPMENT_TEAM = P3R8YQEV4L; 323 | INFOPLIST_FILE = imageTransfer/Info.plist; 324 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 326 | PRODUCT_BUNDLE_IDENTIFIER = com.nordicsemi.imageTransfer; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | SWIFT_VERSION = 4.0; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | }; 331 | name = Debug; 332 | }; 333 | 3034F86B1F7BD46600DA8C88 /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 337 | CODE_SIGN_STYLE = Automatic; 338 | DEVELOPMENT_TEAM = P3R8YQEV4L; 339 | INFOPLIST_FILE = imageTransfer/Info.plist; 340 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 341 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 342 | PRODUCT_BUNDLE_IDENTIFIER = com.nordicsemi.imageTransfer; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SWIFT_VERSION = 4.0; 345 | TARGETED_DEVICE_FAMILY = "1,2"; 346 | }; 347 | name = Release; 348 | }; 349 | /* End XCBuildConfiguration section */ 350 | 351 | /* Begin XCConfigurationList section */ 352 | 3034F8521F7BD46600DA8C88 /* Build configuration list for PBXProject "imageTransfer" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | 3034F8671F7BD46600DA8C88 /* Debug */, 356 | 3034F8681F7BD46600DA8C88 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | 3034F8691F7BD46600DA8C88 /* Build configuration list for PBXNativeTarget "imageTransfer" */ = { 362 | isa = XCConfigurationList; 363 | buildConfigurations = ( 364 | 3034F86A1F7BD46600DA8C88 /* Debug */, 365 | 3034F86B1F7BD46600DA8C88 /* Release */, 366 | ); 367 | defaultConfigurationIsVisible = 0; 368 | defaultConfigurationName = Release; 369 | }; 370 | /* End XCConfigurationList section */ 371 | }; 372 | rootObject = 3034F84F1F7BD46600DA8C88 /* Project object */; 373 | } 374 | -------------------------------------------------------------------------------- /imageTransfer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /imageTransfer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 27/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Camera_iOS_1024_1024_gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Camera_iOS_1024_1024_gradient.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Spotlight-41.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-Small@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-Spotlight-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-Spotlight-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "20x20", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "size" : "20x20", 58 | "idiom" : "ipad", 59 | "filename" : "Icon-Spotlight-42.png", 60 | "scale" : "2x" 61 | }, 62 | { 63 | "size" : "29x29", 64 | "idiom" : "ipad", 65 | "filename" : "Icon-Small.png", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "size" : "29x29", 70 | "idiom" : "ipad", 71 | "filename" : "Icon-Small@2x.png", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "size" : "40x40", 76 | "idiom" : "ipad", 77 | "filename" : "Icon-Spotlight-40.png", 78 | "scale" : "1x" 79 | }, 80 | { 81 | "size" : "40x40", 82 | "idiom" : "ipad", 83 | "filename" : "Icon-Spotlight-40@2x-1.png", 84 | "scale" : "2x" 85 | }, 86 | { 87 | "size" : "76x76", 88 | "idiom" : "ipad", 89 | "filename" : "Icon-76.png", 90 | "scale" : "1x" 91 | }, 92 | { 93 | "size" : "76x76", 94 | "idiom" : "ipad", 95 | "filename" : "Icon-76@2x.png", 96 | "scale" : "2x" 97 | }, 98 | { 99 | "size" : "83.5x83.5", 100 | "idiom" : "ipad", 101 | "filename" : "Icon-iPadPro@2x.png", 102 | "scale" : "2x" 103 | }, 104 | { 105 | "size" : "1024x1024", 106 | "idiom" : "ios-marketing", 107 | "filename" : "Camera_iOS_1024_1024_gradient.png", 108 | "scale" : "1x" 109 | } 110 | ], 111 | "info" : { 112 | "version" : 1, 113 | "author" : "xcode" 114 | } 115 | } -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-60.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-41.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-42.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/By_Nordic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "by_nordic.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "by_nordic@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "by_nordic@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/By_Nordic.imageset/by_nordic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/By_Nordic.imageset/by_nordic.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/By_Nordic.imageset/by_nordic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/By_Nordic.imageset/by_nordic@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/By_Nordic.imageset/by_nordic@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/By_Nordic.imageset/by_nordic@3x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/companyLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "companyLogo-1.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/companyLogo.imageset/companyLogo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/companyLogo.imageset/companyLogo-1.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "ic_bluetooth_disabled_white.png", 5 | "idiom": "universal", 6 | "scale": "1x" 7 | }, 8 | { 9 | "filename": "ic_bluetooth_disabled_white_2x.png", 10 | "idiom": "universal", 11 | "scale": "2x" 12 | }, 13 | { 14 | "filename": "ic_bluetooth_disabled_white_3x.png", 15 | "idiom": "universal", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "author": "xcode", 21 | "version": 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/ic_bluetooth_disabled_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/ic_bluetooth_disabled_white.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/ic_bluetooth_disabled_white_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/ic_bluetooth_disabled_white_2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/ic_bluetooth_disabled_white_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/ic_bluetooth_disabled_white.imageset/ic_bluetooth_disabled_white_3x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/scanning.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "scanning.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "scanning@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "scanning@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/scanning.imageset/scanning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/scanning.imageset/scanning.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/scanning.imageset/scanning@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/scanning.imageset/scanning@2x.png -------------------------------------------------------------------------------- /imageTransfer/Assets.xcassets/scanning.imageset/scanning@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NordicPlayground/iOS-Image-Transfer-Demo/1200f83aba0fb9eb58fa266c66e3fc538551cfe6/imageTransfer/Assets.xcassets/scanning.imageset/scanning@3x.png -------------------------------------------------------------------------------- /imageTransfer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /imageTransfer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 157 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 240 | 253 | 264 | 275 | 281 | 287 | 293 | 299 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 326 | 327 | 328 | 329 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | -------------------------------------------------------------------------------- /imageTransfer/Bluetooth/BluetoothManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothManager.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 27/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreBluetooth 11 | 12 | 13 | class BluetoothManager: NSObject, CBCentralManagerDelegate { 14 | 15 | //MARK: - Properties 16 | let centralManager : CBCentralManager 17 | var targetPeripheral : CameraPeripheral? 18 | var discoveryHandler : ((CBPeripheral, NSNumber) -> ())? 19 | var delegate : BluetoothManagerDelegate? 20 | 21 | required override init() { 22 | centralManager = CBCentralManager() 23 | super.init() 24 | centralManager.delegate = self 25 | } 26 | 27 | public func enable() { 28 | let url = URL(string: UIApplicationOpenSettingsURLString) //for bluetooth setting 29 | let app = UIApplication.shared 30 | app.open(url!, options: [:], completionHandler: nil) 31 | } 32 | 33 | public func scanForPeripherals(withDiscoveryHandler aHandler: @escaping (CBPeripheral, NSNumber)->()) { 34 | guard centralManager.isScanning == false else { 35 | return 36 | } 37 | 38 | discoveryHandler = aHandler 39 | centralManager.scanForPeripherals(withServices: [CameraPeripheral.imageServiceUUID], options: nil) 40 | } 41 | 42 | public func stopScan() { 43 | guard centralManager.isScanning else { 44 | return 45 | } 46 | centralManager.stopScan() 47 | discoveryHandler = nil 48 | } 49 | 50 | public func connect(peripheral: CameraPeripheral) { 51 | guard targetPeripheral == nil else { 52 | // A peripheral is already connected 53 | return 54 | } 55 | targetPeripheral = peripheral 56 | centralManager.connect(peripheral.basePeripheral(), options: nil) 57 | } 58 | 59 | public func disconnect() { 60 | guard targetPeripheral != nil else { 61 | // No device connected at the moment 62 | return 63 | } 64 | centralManager.cancelPeripheralConnection(targetPeripheral!.basePeripheral()) 65 | } 66 | 67 | public var state: CBManagerState { 68 | return centralManager.state 69 | } 70 | 71 | //MARK: - CBCentralManagerDelegate 72 | func centralManagerDidUpdateState(_ central: CBCentralManager) { 73 | delegate?.bluetoothManager(self, didUpdateState: central.state) 74 | } 75 | 76 | func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { 77 | delegate?.bluetoothManager(self, didConnectPeripheral: targetPeripheral!) 78 | } 79 | 80 | func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { 81 | delegate?.bluetoothManager(self, didDisconnectPeripheral: targetPeripheral!) 82 | targetPeripheral = nil 83 | } 84 | 85 | func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { 86 | delegate?.bluetoothManager(self, didDisconnectPeripheral: targetPeripheral!) 87 | targetPeripheral = nil 88 | } 89 | 90 | func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { 91 | discoveryHandler?(peripheral, RSSI) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /imageTransfer/Bluetooth/BluetoothManagerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothManagerDelegate.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 28/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreBluetooth 11 | 12 | protocol BluetoothManagerDelegate { 13 | func bluetoothManager(_ aManager: BluetoothManager, didUpdateState state: CBManagerState) 14 | func bluetoothManager(_ aManager: BluetoothManager, didConnectPeripheral aPeripheral: CameraPeripheral) 15 | func bluetoothManager(_ aManager: BluetoothManager, didDisconnectPeripheral aPeripheral: CameraPeripheral) 16 | } 17 | -------------------------------------------------------------------------------- /imageTransfer/Bluetooth/CameraPeripheral.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralManager.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 27/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreBluetooth 11 | 12 | enum ImageServiceCommand: UInt8 { 13 | case noCommand = 0x00 14 | case startSingleCapture = 0x01 15 | case startStreaming = 0x02 16 | case stopStreaming = 0x03 17 | case changeResolution = 0x04 18 | case changePhy = 0x05 19 | case sendBleParameters = 0x06 20 | 21 | func data() -> Data { 22 | return Data(bytes: [self.rawValue]) 23 | } 24 | } 25 | 26 | enum InfoResponse: UInt8 { 27 | case unknown = 0x00 28 | case imgInfo = 0x01 29 | case bleInfo = 0x02 30 | } 31 | 32 | enum ImageResolution: UInt8 { 33 | case resolution160x120 = 0x01 34 | case resolution320x240 = 0x02 35 | case resolution640x480 = 0x03 36 | case resolution800x600 = 0x04 37 | case resolution1024x768 = 0x05 38 | case resolution1600x1200 = 0x06 39 | 40 | func description() -> String { 41 | switch self { 42 | case .resolution160x120: 43 | return "160x120" 44 | case .resolution320x240: 45 | return "320x240" 46 | case .resolution640x480: 47 | return "640x480" 48 | case .resolution800x600: 49 | return "800x600" 50 | case .resolution1024x768: 51 | return "1024x768" 52 | case .resolution1600x1200: 53 | return "1600x1200" 54 | } 55 | } 56 | } 57 | 58 | enum PhyType: UInt8 { 59 | case phyLE1M = 0x01 60 | case phyLE2M = 0x02 61 | 62 | func description() -> String { 63 | switch self { 64 | case .phyLE1M: 65 | return "LE 1M" 66 | case .phyLE2M: 67 | return "LE 2M" 68 | } 69 | } 70 | } 71 | 72 | class CameraPeripheral: NSObject, CBPeripheralDelegate { 73 | //MARK: - Service Identifiers 74 | public static let imageServiceUUID = CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA3E") 75 | public static let imageRXCharacteristicUUID = CBUUID(string: "6E400002-B5A3-F393-E0A9-E50E24DCCA3E") 76 | public static let imageTXCharacteristicUUID = CBUUID(string: "6E400003-B5A3-F393-E0A9-E50E24DCCA3E") 77 | public static let imageInfoCharacteristicUUID = CBUUID(string: "6E400004-B5A3-F393-E0A9-E50E24DCCA3E") 78 | 79 | //MARK: - Properties 80 | public var targetPeripheral : CBPeripheral 81 | public var delegate : CameraPeripheralDelegate? 82 | private var imageInfoCharacteristic : CBCharacteristic! 83 | private var imageRXCharacteristic : CBCharacteristic! 84 | private var imageTXCharacteristic : CBCharacteristic! 85 | private var snapshotData : Data = Data() 86 | private var currentImageSize : Int = 0 87 | private var imageStartTime : TimeInterval = 0 88 | private var imageElapsedTime : TimeInterval = 0 89 | private var streamStartTime : TimeInterval = 0 90 | private var transferRate : Double = 0 91 | private var framesCount : Int = 0 92 | 93 | required init(withPeripheral aPeripheral: CBPeripheral) { 94 | targetPeripheral = aPeripheral 95 | super.init() 96 | targetPeripheral.delegate = self 97 | } 98 | 99 | public func basePeripheral() -> CBPeripheral { 100 | return targetPeripheral 101 | } 102 | 103 | //MARK: - ImageService API 104 | public func getBleParameters() { 105 | targetPeripheral.writeValue(ImageServiceCommand.sendBleParameters.data(), for: imageRXCharacteristic, type: .withoutResponse) 106 | } 107 | 108 | public func changePhy(_ aPhy: PhyType) { 109 | var changePhyCommand = ImageServiceCommand.changePhy.data() 110 | //The Raw value is decremented because phy command takes 0 for Phy1 and 1 for Phy2 111 | //However, the reported values in the ble connection info update will be 1 for Phy1 and 2 for Phy2 112 | //So the change phy command is offset by 1 to compensate. 113 | //See: https://github.com/NordicPlayground/nrf52-ble-image-transfer-demo/blob/master/main.c#L905 114 | changePhyCommand.append(contentsOf: [aPhy.rawValue - 1]) 115 | targetPeripheral.writeValue(changePhyCommand, for: imageRXCharacteristic, type: .withoutResponse) 116 | } 117 | 118 | public func changeResolution(_ aResolution : ImageResolution) { 119 | var changeResolutionCommand = ImageServiceCommand.changeResolution.data() 120 | changeResolutionCommand.append(contentsOf: [aResolution.rawValue]) 121 | targetPeripheral.writeValue(changeResolutionCommand, for: imageRXCharacteristic, type: .withoutResponse) 122 | } 123 | 124 | public func stopStream() { 125 | snapshotData = Data() 126 | targetPeripheral.writeValue(ImageServiceCommand.stopStreaming.data(), for: imageRXCharacteristic, type: .withoutResponse) 127 | } 128 | 129 | public func startStream() { 130 | streamStartTime = Date().timeIntervalSince1970 131 | framesCount = 0 132 | snapshotData = Data() 133 | targetPeripheral.writeValue(ImageServiceCommand.startStreaming.data(), for: imageRXCharacteristic, type: .withoutResponse) 134 | } 135 | 136 | public func takeSnapshot() { 137 | streamStartTime = Date().timeIntervalSince1970 138 | framesCount = 0 139 | snapshotData = Data() 140 | targetPeripheral.writeValue(ImageServiceCommand.startSingleCapture.data(), for: imageRXCharacteristic, type: .withoutResponse) 141 | } 142 | 143 | //MARK: - Bluetooth API 144 | public func discoverServices() { 145 | targetPeripheral.discoverServices([CameraPeripheral.imageServiceUUID]) 146 | } 147 | 148 | public func enableNotifications() { 149 | guard imageRXCharacteristic != nil && imageTXCharacteristic != nil && imageInfoCharacteristic != nil else { 150 | return 151 | } 152 | targetPeripheral.setNotifyValue(true, for: imageTXCharacteristic) 153 | targetPeripheral.setNotifyValue(true, for: imageInfoCharacteristic) 154 | } 155 | 156 | //MARK: - CBPeripheralDelegate 157 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { 158 | guard error == nil else { 159 | delegate?.cameraPeripheral(self, failedWithError: error!) 160 | return 161 | } 162 | 163 | if let services = peripheral.services { 164 | // Check if the required service has been found 165 | guard services.count == 1 else { 166 | delegate?.cameraPeripheralNotSupported(self) 167 | return 168 | } 169 | 170 | // If the service was found, discover required characteristics 171 | for aService in services { 172 | if aService.uuid == CameraPeripheral.imageServiceUUID { 173 | peripheral.discoverCharacteristics([CameraPeripheral.imageRXCharacteristicUUID, 174 | CameraPeripheral.imageTXCharacteristicUUID, 175 | CameraPeripheral.imageInfoCharacteristicUUID], 176 | for: aService) 177 | } 178 | } 179 | } 180 | } 181 | 182 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { 183 | guard error == nil else { 184 | delegate?.cameraPeripheral(self, failedWithError: error!) 185 | return 186 | } 187 | 188 | if let characteristics = service.characteristics { 189 | // Assign references 190 | for aCharacteristic in characteristics { 191 | if aCharacteristic.uuid == CameraPeripheral.imageTXCharacteristicUUID { 192 | imageTXCharacteristic = aCharacteristic 193 | } else if aCharacteristic.uuid == CameraPeripheral.imageRXCharacteristicUUID { 194 | imageRXCharacteristic = aCharacteristic 195 | } else if aCharacteristic.uuid == CameraPeripheral.imageInfoCharacteristicUUID { 196 | imageInfoCharacteristic = aCharacteristic 197 | } 198 | } 199 | 200 | // Check if all required characteristics were found 201 | guard imageRXCharacteristic != nil && imageTXCharacteristic != nil && imageInfoCharacteristic != nil else { 202 | delegate?.cameraPeripheralNotSupported(self) 203 | return 204 | } 205 | 206 | // Notify the delegate that the device is supported and ready 207 | delegate?.cameraPeripheralDidBecomeReady(self) 208 | } 209 | } 210 | 211 | public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { 212 | guard error == nil else { 213 | delegate?.cameraPeripheral(self, failedWithError: error!) 214 | return 215 | } 216 | 217 | if characteristic == imageInfoCharacteristic { 218 | snapshotData.removeAll() 219 | let data = characteristic.value! 220 | let messageType = InfoResponse(rawValue: data[0]) ?? .unknown 221 | switch messageType { 222 | case .imgInfo: 223 | imageStartTime = Date().timeIntervalSince1970 224 | transferRate = 0 225 | currentImageSize = data.subdata(in: 1..<5).withUnsafeBytes { $0.pointee } 226 | case .bleInfo: 227 | let mtuSize = data.subdata(in: 1..<3).withUnsafeBytes { (ptr: UnsafePointer) -> UInt16 in return ptr.pointee } 228 | let connectionInterval = Float(data.subdata(in: 3..<5).withUnsafeBytes { (ptr: UnsafePointer) -> UInt16 in return ptr.pointee }) * 1.25 229 | 230 | //Phy types here will be the UInt8 value 1 or 2 for 1Mb and 2Mb respectively. 231 | let txPhy = PhyType(rawValue: data[5]) ?? .phyLE1M 232 | let rxPhy = PhyType(rawValue: data[6]) ?? .phyLE1M 233 | delegate?.cameraPeripheral(self, didUpdateParametersWithMTUSize: mtuSize, connectionInterval: connectionInterval, txPhy: txPhy, andRxPhy: rxPhy) 234 | default: 235 | break 236 | } 237 | } else if characteristic == imageTXCharacteristic { 238 | if let dataChunk = characteristic.value { 239 | snapshotData.append(dataChunk) 240 | } 241 | let now = Date().timeIntervalSince1970 242 | imageElapsedTime = now - imageStartTime 243 | transferRate = Double(snapshotData.count) / imageElapsedTime * 8.0 / 1000.0 // convert bytes per second to kilobits per second 244 | 245 | if snapshotData.count == currentImageSize { 246 | framesCount += 1 247 | delegate?.cameraPeripheral(self, imageProgress: 1.0, transferRateInKbps: transferRate) 248 | delegate?.cameraPeripheral(self, didReceiveImageData: snapshotData, withFps: Double(framesCount) / (now - streamStartTime)) 249 | snapshotData.removeAll() 250 | } else { 251 | let completion = Float(snapshotData.count) / Float(currentImageSize) 252 | delegate?.cameraPeripheral(self, imageProgress: completion, transferRateInKbps: transferRate) 253 | } 254 | } 255 | } 256 | 257 | public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { 258 | guard error == nil else { 259 | delegate?.cameraPeripheral(self, failedWithError: error!) 260 | return 261 | } 262 | if imageTXCharacteristic.isNotifying && imageInfoCharacteristic.isNotifying { 263 | delegate?.cameraPeripheralDidStart(self) 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /imageTransfer/Bluetooth/CameraPeripheralDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraPeripheralDelegate.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 28/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CameraPeripheralDelegate { 12 | /// Called when camera has all required services and characteristics discovered. 13 | func cameraPeripheralDidBecomeReady(_ aPeripheral: CameraPeripheral) 14 | /// Called when sleected device does not have required services. 15 | func cameraPeripheralNotSupported(_ aPeripheral: CameraPeripheral) 16 | /// Called when notifications were enabled. 17 | func cameraPeripheralDidStart(_ aPeripheral: CameraPeripheral) 18 | /// Called when and error occured. 19 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, failedWithError error: Error) 20 | /// Full image data is complete. 21 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, didReceiveImageData someData: Data, withFps fps: Double) 22 | /// Value between 0 and 1 presenting completion in percentage. 23 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, imageProgress: Float, transferRateInKbps: Double) 24 | /// Connection parameters did change. 25 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, didUpdateParametersWithMTUSize mtuSize: UInt16, connectionInterval connInterval: Float, txPhy: PhyType, andRxPhy rxPhy: PhyType) 26 | } 27 | -------------------------------------------------------------------------------- /imageTransfer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Image Transfer 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleDefault 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /imageTransfer/ViewControllers/CameraViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraViewController.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 28/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreBluetooth 11 | 12 | class CameraViewController: UIViewController, CameraPeripheralDelegate, BluetoothManagerDelegate { 13 | 14 | //MARK: - Outlets 15 | @IBOutlet weak var imageProgressIndicator: UIProgressView! 16 | @IBOutlet weak var cameraButton: UIButton! 17 | @IBOutlet weak var videoButton: UIButton! 18 | @IBOutlet weak var cameraImageView: UIImageView! 19 | @IBOutlet weak var resolutionButton: UIButton! 20 | @IBOutlet weak var phyButton: UIButton! 21 | @IBOutlet weak var connectionIntervalLabel: UILabel! 22 | @IBOutlet weak var mtuLabel: UILabel! 23 | @IBOutlet weak var transferRateLabel: UILabel! 24 | @IBOutlet weak var fpsLabel: UILabel! 25 | @IBOutlet weak var fpsLabelFrame: UIView! 26 | 27 | //MARK: - Actions 28 | @IBAction func phyButtonTapped(_ sender: Any) { 29 | handlePhyButtonTapped() 30 | } 31 | @IBAction func resolutionButtonTapped(_ sender: Any) { 32 | handleResolutionButtonTapped() 33 | } 34 | @IBAction func videoButtonTapped(_ sender: Any) { 35 | handleVideoButtonTapped() 36 | } 37 | @IBAction func cameraButtonTapped(_ sender: Any) { 38 | handleCameraButtonTapped() 39 | } 40 | 41 | //MARK: - Properties 42 | var bluetoothManager : BluetoothManager! 43 | var targetPeripheral : CameraPeripheral! 44 | private var isStreaming : Bool = false 45 | private var currentResolution : ImageResolution = .resolution160x120 46 | private var currentPhy : PhyType = .phyLE1M 47 | private var loadingView: UIAlertController? 48 | 49 | override func viewWillAppear(_ animated: Bool) { 50 | super.viewWillAppear(animated) 51 | bluetoothManager.delegate = self 52 | targetPeripheral.delegate = self 53 | 54 | updateResolutionButtonTitle(currentResolution) 55 | imageProgressIndicator.progress = 0 56 | 57 | setRoundedCornerRadius(aRadius: 10, forView: cameraImageView) 58 | setRoundedCornerRadius(aRadius: 10, forView: videoButton) 59 | setRoundedCornerRadius(aRadius: 10, forView: cameraButton) 60 | setRoundedCornerRadius(aRadius: 10, forView: resolutionButton) 61 | setRoundedCornerRadius(aRadius: 10, forView: phyButton) 62 | setRoundedCornerRadius(aRadius: 10, forView: fpsLabelFrame) 63 | startCamera() 64 | } 65 | 66 | override func viewWillDisappear(_ animated: Bool) { 67 | bluetoothManager.disconnect() 68 | } 69 | 70 | private func startCamera() { 71 | loadingView = UIAlertController(title: "Status", message: "Starting notifications...", preferredStyle: .alert) 72 | loadingView!.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in 73 | self.navigationController!.popViewController(animated: true) 74 | }) 75 | present(loadingView!, animated: false) { 76 | self.cameraPeripheralDidBecomeReady(self.targetPeripheral) 77 | } 78 | } 79 | 80 | private func setRoundedCornerRadius(aRadius : CGFloat, forView aView : UIView) { 81 | aView.layer.cornerRadius = aRadius 82 | aView.layer.masksToBounds = true 83 | } 84 | 85 | private func handlePhyButtonTapped() { 86 | if currentPhy == .phyLE2M { 87 | currentPhy = .phyLE1M 88 | } else { 89 | currentPhy = .phyLE2M 90 | } 91 | targetPeripheral.changePhy(currentPhy) 92 | updatePhyButtonTitle(currentPhy) 93 | } 94 | 95 | private func handleResolutionButtonTapped() { 96 | if currentResolution == .resolution1600x1200 { 97 | currentResolution = .resolution160x120 98 | } else { 99 | var rawValue = currentResolution.rawValue 100 | rawValue = rawValue + 1 101 | currentResolution = ImageResolution(rawValue: rawValue)! 102 | } 103 | targetPeripheral.changeResolution(currentResolution) 104 | updateResolutionButtonTitle(currentResolution) 105 | } 106 | 107 | private func handleVideoButtonTapped() { 108 | imageProgressIndicator.progress = 0 109 | 110 | if isStreaming { 111 | targetPeripheral.stopStream() 112 | videoButton.setTitle("📹 Start stream", for: .normal) 113 | } else { 114 | targetPeripheral.startStream() 115 | videoButton.setTitle("📹 Stop stream", for: .normal) 116 | } 117 | isStreaming = !isStreaming 118 | } 119 | 120 | private func handleCameraButtonTapped() { 121 | imageProgressIndicator.progress = 0 122 | targetPeripheral.takeSnapshot() 123 | } 124 | 125 | private func updateResolutionButtonTitle(_ aResolution: ImageResolution) { 126 | resolutionButton.setTitle("Resolution: \(aResolution.description())", for: .normal) 127 | } 128 | 129 | private func updatePhyButtonTitle(_ aPhy: PhyType) { 130 | phyButton.setTitle("PHY: \(aPhy.description())", for: .normal) 131 | } 132 | 133 | //MARK: - CameraPeripheralDelegate 134 | func cameraPeripheralDidBecomeReady(_ aPeripheral: CameraPeripheral) { 135 | loadingView!.message = "Reading parameters..." 136 | aPeripheral.enableNotifications() 137 | } 138 | 139 | func cameraPeripheralDidStart(_ aPeripheral: CameraPeripheral) { 140 | aPeripheral.getBleParameters() 141 | } 142 | 143 | func cameraPeripheralNotSupported(_ aPeripheral: CameraPeripheral) { 144 | // NOOP 145 | } 146 | 147 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, failedWithError error: Error) { 148 | let alert = UIAlertController(title: "Status", message: "Error: \(error.localizedDescription)", preferredStyle: .alert) 149 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in 150 | self.navigationController!.popViewController(animated: true) 151 | }) 152 | present(alert, animated: true, completion: nil) 153 | } 154 | 155 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, didReceiveImageData someData: Data, withFps fps: Double) { 156 | if let anImage = UIImage(data: someData) { 157 | cameraImageView.image = anImage 158 | } 159 | fpsLabel.text = String(format:"FPS: %0.2f", fps) 160 | } 161 | 162 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, imageProgress: Float, transferRateInKbps: Double) { 163 | imageProgressIndicator.progress = imageProgress 164 | transferRateLabel.text = String(format:"%0.2f kbps", transferRateInKbps) 165 | } 166 | 167 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, didUpdateParametersWithMTUSize mtuSize: UInt16, connectionInterval connInterval: Float, txPhy: PhyType, andRxPhy rxPhy: PhyType) { 168 | if rxPhy == .phyLE1M && currentPhy == .phyLE2M { 169 | print("2mbps not supported") 170 | currentPhy = .phyLE1M 171 | phyButton.isEnabled = false 172 | phyButton.backgroundColor = UIColor.gray 173 | } 174 | if rxPhy == .phyLE2M && currentPhy == .phyLE1M { 175 | print("Changing back to PHY LE 1M not supported") 176 | currentPhy = .phyLE2M 177 | } 178 | 179 | connectionIntervalLabel.text = "\(connInterval) ms" 180 | mtuLabel.text = "\(mtuSize)(+3) bytes" 181 | updatePhyButtonTitle(rxPhy) 182 | 183 | loadingView?.dismiss(animated: true) { 184 | self.loadingView = nil 185 | } 186 | } 187 | 188 | //MARK: - BluetoothManagerDelegate 189 | 190 | func bluetoothManager(_ aManager: BluetoothManager, didDisconnectPeripheral aPeripheral: CameraPeripheral) { 191 | print("Disconnected") 192 | videoButton.isEnabled = false 193 | cameraButton.isEnabled = false 194 | resolutionButton.isEnabled = false 195 | phyButton.isEnabled = false 196 | videoButton.backgroundColor = UIColor.gray 197 | cameraButton.backgroundColor = UIColor.gray 198 | resolutionButton.backgroundColor = UIColor.gray 199 | phyButton.backgroundColor = UIColor.gray 200 | 201 | let alert = UIAlertController(title: "Status", message: "Device disconnected", preferredStyle: .alert) 202 | alert.addAction(UIAlertAction(title: "OK", style: .default) { action in 203 | self.navigationController!.popViewController(animated: true) 204 | }) 205 | present(alert, animated: true, completion: nil) 206 | } 207 | 208 | func bluetoothManager(_ aManager: BluetoothManager, didUpdateState state: CBManagerState) { 209 | if state != .poweredOn { 210 | print("Bluetooth OFF") 211 | bluetoothManager(aManager, didDisconnectPeripheral: targetPeripheral) 212 | } 213 | } 214 | 215 | func bluetoothManager(_ aManager: BluetoothManager, didConnectPeripheral aPeripheral: CameraPeripheral) { 216 | // NOOP 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /imageTransfer/ViewControllers/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 27/09/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreBluetooth 11 | 12 | class MainViewController: UIViewController, BluetoothManagerDelegate, UITableViewDelegate, UITableViewDataSource, CameraPeripheralDelegate { 13 | 14 | //MARK: - Outlets and Actions 15 | @IBOutlet weak var noTargetsLabel: UIView! 16 | @IBOutlet weak var tableView: UITableView! 17 | @IBOutlet weak var bluetoothDisabledView: UIView! 18 | 19 | @IBAction func enableBluetooth(_ sender: UIButton) { 20 | bluetoothManager.enable() 21 | } 22 | 23 | //MARK: - Properties 24 | private var peripherals: [CameraPeripheral] = [] 25 | private var bluetoothManager: BluetoothManager 26 | private var loadingView: UIAlertController? 27 | 28 | override var preferredStatusBarStyle: UIStatusBarStyle { 29 | return .lightContent 30 | } 31 | 32 | //MARK: - UIViewController 33 | required init?(coder aDecoder: NSCoder) { 34 | bluetoothManager = BluetoothManager() 35 | super.init(coder: aDecoder) 36 | } 37 | 38 | override func viewWillAppear(_ animated: Bool) { 39 | bluetoothManager.delegate = self 40 | if bluetoothManager.state == .poweredOn { 41 | startScan() 42 | } else { 43 | bluetoothDisabledView.alpha = 1 44 | } 45 | } 46 | 47 | override func viewWillDisappear(_ animated: Bool) { 48 | stopScan() 49 | } 50 | 51 | //MARK: - UITableViewDataSource 52 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 53 | // Skip the Header Clicked event 54 | guard indexPath.row > 0 else { 55 | return 56 | } 57 | // Deselect the row (there will be short click animation) 58 | tableView.deselectRow(at: indexPath, animated: true) 59 | 60 | guard bluetoothManager.state == .poweredOn else { 61 | return 62 | } 63 | 64 | // Stop scanning and connect to selected peripheral 65 | stopScan() 66 | connect(to: peripherals[indexPath.row - 1]) 67 | } 68 | 69 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 70 | return peripherals.count + 1 // 1 for the section header with activity indicator 71 | } 72 | 73 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 74 | if indexPath.row == 0 { 75 | let header = tableView.dequeueReusableCell(withIdentifier: "section_cell", for: indexPath) as! SectionTableViewCell 76 | header.setEnabled(enabled: bluetoothManager.state == .poweredOn) 77 | return header 78 | } 79 | let cell = tableView.dequeueReusableCell(withIdentifier: "device_cell", for: indexPath) 80 | cell.textLabel?.text = peripherals[indexPath.row - 1].basePeripheral().name ?? "No name" 81 | return cell 82 | } 83 | 84 | //MARK: - CameraPeripheralDelegate 85 | func cameraPeripheralDidBecomeReady(_ aPeripheral: CameraPeripheral) { 86 | startCamera(with: aPeripheral) 87 | } 88 | 89 | func cameraPeripheralNotSupported(_ aPeripheral: CameraPeripheral) { 90 | print("Device not supported") 91 | loadingView!.message = "Device not supported" 92 | } 93 | 94 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, failedWithError error: Error) { 95 | print("Error: \(error.localizedDescription)") 96 | loadingView!.message = "Operation failed:\n\(error.localizedDescription)" 97 | } 98 | 99 | func cameraPeripheralDidStart(_ aPeripheral: CameraPeripheral) { 100 | //NOOP 101 | } 102 | 103 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, didReceiveImageData someData: Data, withFps fps: Double) { 104 | //NOOP 105 | } 106 | 107 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, imageProgress: Float, transferRateInKbps: Double) { 108 | //NOOP 109 | } 110 | 111 | func cameraPeripheral(_ aPeripheral: CameraPeripheral, didUpdateParametersWithMTUSize mtuSize: UInt16, connectionInterval connInterval: Float, txPhy: PhyType, andRxPhy rxPhy: PhyType) { 112 | //NOOP 113 | } 114 | 115 | //MARK: - Implementation 116 | private func startScan() { 117 | // If the scanning was started again, remove all previous results to aviod duplicates 118 | if peripherals.isEmpty == false { 119 | peripherals.removeAll() 120 | tableView.reloadData() 121 | 122 | UIView.animate(withDuration: 0.2) { 123 | self.noTargetsLabel.alpha = 1 124 | } 125 | } 126 | 127 | print("Scanning started") 128 | bluetoothManager.scanForPeripherals(withDiscoveryHandler: { (aPeripheral, RSSI) in 129 | UIView.animate(withDuration: 0.5) { 130 | self.noTargetsLabel.alpha = 0 131 | } 132 | self.tableView.beginUpdates() 133 | self.peripherals.append(CameraPeripheral(withPeripheral: aPeripheral)) 134 | self.tableView.insertRows(at: [IndexPath(row: 1, section: 0)], with: .automatic) 135 | self.tableView.endUpdates() 136 | }) 137 | } 138 | 139 | private func stopScan() { 140 | bluetoothManager.stopScan() 141 | print("Scanning stopped") 142 | } 143 | 144 | private func connect(to aPeripheral: CameraPeripheral) { 145 | loadingView = UIAlertController(title: "Status", message: "Connecting to the camera...", preferredStyle: .alert) 146 | loadingView!.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in 147 | // Clicking an action automatically dismisses the alert controller 148 | self.bluetoothManager.disconnect() 149 | self.loadingView = nil 150 | }) 151 | present(loadingView!, animated: true) { 152 | aPeripheral.delegate = self 153 | print("Connecting...") 154 | self.bluetoothManager.connect(peripheral: aPeripheral) 155 | } 156 | } 157 | 158 | private func discoverServices(for aPeripheral: CameraPeripheral) { 159 | print("Discovering services...") 160 | loadingView!.message = "Discovering services..." 161 | aPeripheral.discoverServices() 162 | } 163 | 164 | private func startCamera(with aPeripheral: CameraPeripheral) { 165 | loadingView!.dismiss(animated: false) 166 | print("Ready") 167 | performSegue(withIdentifier: "showCameraControl", sender: aPeripheral) 168 | } 169 | 170 | //MARK: - BluetoothManagerDelegate 171 | func bluetoothManager(_ aManager: BluetoothManager, didUpdateState state: CBManagerState) { 172 | if state == .poweredOn { 173 | print("Bluetooth ON") 174 | bluetoothDisabledView.alpha = 0 175 | startScan() 176 | } else { 177 | stopScan() 178 | bluetoothDisabledView.alpha = 1 179 | } 180 | tableView.reloadData() 181 | } 182 | 183 | func bluetoothManager(_ aManager: BluetoothManager, didConnectPeripheral aPeripheral: CameraPeripheral) { 184 | print("Connected") 185 | discoverServices(for: aPeripheral) 186 | } 187 | 188 | func bluetoothManager(_ aManager: BluetoothManager, didDisconnectPeripheral aPeripheral: CameraPeripheral) { 189 | print("Disconnected") 190 | loadingView?.dismiss(animated: true) { 191 | self.loadingView = nil 192 | } 193 | startScan() 194 | } 195 | 196 | //MARK: - Segue handling 197 | override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { 198 | return identifier == "showCameraControl" 199 | } 200 | 201 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 202 | if segue.identifier == "showCameraControl" { 203 | let cameraPeriperal = sender as! CameraPeripheral 204 | let cameraView = segue.destination as! CameraViewController 205 | cameraView.bluetoothManager = bluetoothManager 206 | cameraView.targetPeripheral = cameraPeriperal 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /imageTransfer/ViewControllers/RootNavigationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootNavigationViewController.swift 3 | // imageTransfer 4 | // 5 | // Created by Mostafa Berg on 13/12/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RootNavigationViewController: UINavigationController { 12 | override var preferredStatusBarStyle: UIStatusBarStyle { 13 | return .lightContent 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /imageTransfer/ViewControllers/SectionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionTableViewCell.swift 3 | // imageTransfer 4 | // 5 | // Created by Aleksander Nowakowski on 12/10/2017. 6 | // Copyright © 2017 Nordic Semiconductor ASA. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SectionTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 14 | @IBOutlet weak var label: UILabel! 15 | 16 | func setEnabled(enabled: Bool) { 17 | if enabled { 18 | label.text = "SCANNING FOR CAMERA DEVICES..." 19 | activityIndicator.isHidden = false 20 | activityIndicator.startAnimating() 21 | } else { 22 | label.text = "DEVICES" 23 | activityIndicator.isHidden = true 24 | activityIndicator.stopAnimating() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # iOS-image-transfer-demo 2 | 3 | This app allows images to be streamed from an nRF52 kit with a connected camera sensor to the application, showing the image and measuring the transfer speed in the process. 4 | 5 | Different image resolutions can be selected in the app, and the BLE phy can be changed between 1Mbps and 2Mbps to demonstrate the difference (this requires a phone that supports 2Mbps). 6 | 7 | ### Installation instructions: 8 | 9 | - Download the appropriate firmware for your development kit [Here](https://github.com/NordicPlayground/nrf52-ble-image-transfer-demo) 10 | - Power on your Development Kit. 11 | - Plug in the Development Kit over USB to your computer. 12 | - A new drive will appear on your computer (Mass Storage Device). 13 | - Drag (or copy/paste) the appropriate hex file to that device. 14 | - The Development Kit will now disconnect and reconnect, it is not programmed and ready. 15 | - Open the Xcode project and build against your connected device (**Note:** iOS Simulators do not have BLE capabilities, so this demo will not work on the Simulator). 16 | 17 | ### App instructions: 18 | 19 | - Launch the Image Transfer Demo app on your device. 20 | - The app will scan for nearby peripherals (**Note:** If you can't discover your peripheral, make sure it's powered on and functional). 21 | - Select the target peripheral from the list. 22 | - The app will now connect and discover the services and characteristics on the peripheral. 23 | - Press the PHY: LE 1M button to switch to 2M mode (if the iOS device does not support it, it'll grey out and stay at 1M), if 2M is supported, it'll switch to 2M and the button will grey out as it's not possible to switch back from 2M to 1M connections. 24 | - Supported connections: 25 | - 1M 26 | - 2M 27 | - Press the Resolution button to switch the resolution between: 28 | - 160 x 120 29 | - 320 x 240 30 | - 640 x 480 31 | - 800 x 600 32 | - 1024 x 768 33 | - 1600 x 1200 34 | - Press take snapshot to receive an image from the Development Kit 35 | - Press Start stream to start sending continuous images from the Development Kit (Pressing the button again will cause the stream to stop). 36 | - During streaming and taking snapshots, the following labels will update to notify of connection properties: 37 | - Connection Interval: Displays the current connection interval. 38 | - MTU: Displays the current MTU. 39 | - Trasnfer rate: distlays the instantaneous transfer rate. 40 | - FPS: This will display the calculated framerate according to the current speed. 41 | --------------------------------------------------------------------------------